import { app, screen, safeStorage, ipcMain, BrowserWindow, nativeTheme, dialog, nativeImage } from "electron"; import { join } from "path"; import { readFileSync, writeFile } from "fs"; import Store from "electron-store"; import Redis from "ioredis"; import __cjs_mod__ from "node:module"; const __filename = import.meta.filename; const __dirname = import.meta.dirname; const require2 = __cjs_mod__.createRequire(import.meta.url); 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) { try { return JSON.parse(str); } catch { return null; } } function getLastState() { 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 { } return { x: null, y: null, width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, maximized: false }; } function saveState(win) { const bounds = win.getBounds(); const state = { ...bounds, maximized: win.isMaximized() }; writeFile(STATE_FILE, JSON.stringify(state), "utf-8", () => { }); } function restoreAndTrack(win) { 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)); } const store = new Store({ name: "jrdm-config", defaults: { connections: [], settings: { theme: "system", locale: "en", fontSize: 13, scanCount: 500 } } }); function encrypt(text) { if (!safeStorage.isEncryptionAvailable()) { return Buffer.from(text).toString("base64"); } return safeStorage.encryptString(text).toString("base64"); } function decrypt(encoded) { if (!safeStorage.isEncryptionAvailable()) { return Buffer.from(encoded, "base64").toString("utf-8"); } return safeStorage.decryptString(Buffer.from(encoded, "base64")); } const clients = /* @__PURE__ */ new Map(); function buildRedisOptions(opts) { const options = { host: opts.host, port: opts.port, username: opts.username || void 0, password: opts.password || void 0, stringNumbers: true, enableReadyCheck: true, family: 0, connectTimeout: 1e4, retryStrategy(times) { if (times > 3) return null; return Math.min(times * 200, 1e3); }, maxRetriesPerRequest: 3 }; if (opts.tls) { options.tls = { rejectUnauthorized: false, checkServerIdentity: () => void 0 }; } return options; } function getClient(id) { return clients.get(id); } function isClientConnected(id) { const client = clients.get(id); return client?.status === "ready"; } async function connect(id, opts) { 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) => { redis.disconnect(); reject(err); }); setTimeout(() => { if (!clients.has(id)) { redis.disconnect(); reject(new Error("连接超时,请检查主机和端口")); } }, 12e3); }); } async function disconnect(id) { const client = clients.get(id); if (client) { client.disconnect(); clients.delete(id); } } async function getKeyType(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.type(key); } async function getKeyTTL(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.ttl(key); } async function deleteKey(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); await client.del(key); } async function setKeyTTL(id, key, ttl) { 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); } } async function renameKey(id, oldKey, newKey) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); await client.rename(oldKey, newKey); } async function existsKey(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return await client.exists(key) === 1; } async function batchDelete(id, keys) { 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; } async function memoryUsage(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.memory("USAGE", key); } async function scanKeys(id, cursor, pattern, count) { 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 }; } async function getStringValue(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.get(key); } async function setStringValue(id, key, value) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); await client.set(key, value); } async function getHashFields(id, key, cursor, count) { 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 = []; for (let i = 0; i < raw.length; i += 2) { fields.push([raw[i], raw[i + 1]]); } return { cursor: nextCursor, fields }; } async function setHashField(id, key, field, value) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); await client.hset(key, field, value); } async function deleteHashField(id, key, field) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); await client.hdel(key, field); } async function listRange(id, key, start, stop) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.lrange(key, start, stop); } async function listLength(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.llen(key); } async function listPush(id, key, ...values) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.rpush(key, ...values); } async function listSet(id, key, index, value) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); await client.lset(key, index, value); } async function listRemove(id, key, count, value) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.lrem(key, count, value); } async function setMembers(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.smembers(key); } async function setSize(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.scard(key); } async function setAdd(id, key, ...members) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.sadd(key, ...members); } async function setRemove(id, key, ...members) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.srem(key, ...members); } async function zsetRange(id, key, start, stop, withScores) { 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); } async function zsetSize(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.zcard(key); } async function zsetAdd(id, key, score, member) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.zadd(key, score, member); } async function zsetRemove(id, key, ...members) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.zrem(key, ...members); } async function streamInfo(id, key) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.xinfo("STREAM", key); } async function streamRange(id, key, start, end, count) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.xrange(key, start, end, "COUNT", count); } async function streamAdd(id, key, streamId, fieldValues) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.xadd(key, streamId, ...fieldValues); } async function streamTrim(id, key, maxLen) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.xtrim(key, "MAXLEN", maxLen); } async function streamDel(id, key, ...ids) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.xdel(key, ...ids); } async function execute(id, command, args) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.call(command, ...args); } async function getServerInfo(id) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.info(); } async function selectDb(id, db) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); await client.select(db); } async function dbSize(id) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.dbsize(); } async function ping(id) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.ping(); } async function slowLogGet(id, count) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.slowlog("GET", count); } async function slowLogLen(id) { const client = getClient(id); if (!client) throw new Error(`Client ${id} not found`); return client.slowlog("LEN"); } function registerIpcHandlers() { 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()); ipcMain.handle("theme:get", () => nativeTheme.shouldUseDarkColors); ipcMain.on("theme:set", (_e, theme) => { nativeTheme.themeSource = theme; }); ipcMain.handle("dialog:openFile", async (_e, options) => { 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; } }); ipcMain.handle("credential:encrypt", (_e, text) => encrypt(text)); ipcMain.handle("credential:decrypt", (_e, encoded) => decrypt(encoded)); ipcMain.handle("storage:getConnections", () => store.get("connections", [])); ipcMain.handle("storage:saveConnection", (_e, conn) => { const connections = store.get("connections", []); const idx = connections.findIndex((c) => 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) => { const connections = store.get("connections", []).filter((c) => c.id !== id); store.set("connections", connections); return connections; }); ipcMain.handle("storage:reorderConnections", (_e, ids) => { const connections = store.get("connections", []); const reordered = ids.map((id, i) => { const conn = connections.find((c) => c.id === id); conn.order = i; return conn; }); store.set("connections", reordered); return reordered; }); ipcMain.handle("storage:getSettings", () => store.get("settings")); ipcMain.handle("storage:saveSettings", (_e, settings) => { store.set("settings", settings); }); ipcMain.handle("redis:connect", async (_e, id, opts) => { try { await connect(id, opts); return { success: true }; } catch (err) { return { success: false, error: err.message }; } }); ipcMain.handle("redis:disconnect", async (_e, id) => { await disconnect(id); }); ipcMain.handle("redis:ping", async (_e, id) => { return ping(id); }); ipcMain.handle("redis:isConnected", (_e, id) => { return isClientConnected(id); }); ipcMain.handle("redis:execute", async (_e, id, command, args) => { return execute(id, command, args); }); ipcMain.handle("redis:getInfo", async (_e, id) => { return getServerInfo(id); }); ipcMain.handle("redis:selectDb", async (_e, id, db) => { await selectDb(id, db); }); ipcMain.handle("redis:dbSize", async (_e, id) => { return dbSize(id); }); ipcMain.handle("redis:slowLogGet", async (_e, id, count) => { return slowLogGet(id, count); }); ipcMain.handle("redis:slowLogLen", async (_e, id) => { return slowLogLen(id); }); ipcMain.handle("redis:scanKeys", async (_e, id, cursor, pattern, count) => { return scanKeys(id, cursor, pattern, count); }); ipcMain.handle("redis:getKeyType", async (_e, id, key) => { return getKeyType(id, key); }); ipcMain.handle("redis:getKeyTTL", async (_e, id, key) => { return getKeyTTL(id, key); }); ipcMain.handle("redis:setTTL", async (_e, id, key, ttl) => { await setKeyTTL(id, key, ttl); }); ipcMain.handle("redis:deleteKey", async (_e, id, key) => { await deleteKey(id, key); }); ipcMain.handle("redis:renameKey", async (_e, id, oldKey, newKey) => { await renameKey(id, oldKey, newKey); }); ipcMain.handle("redis:existsKey", async (_e, id, key) => { return existsKey(id, key); }); ipcMain.handle("redis:batchDelete", async (_e, id, keys) => { return batchDelete(id, keys); }); ipcMain.handle("redis:memoryUsage", async (_e, id, key) => { return memoryUsage(id, key); }); ipcMain.handle("redis:getString", async (_e, id, key) => { return getStringValue(id, key); }); ipcMain.handle("redis:setString", async (_e, id, key, value) => { await setStringValue(id, key, value); }); ipcMain.handle("redis:hashScan", async (_e, id, key, cursor, count) => { return getHashFields(id, key, cursor, count); }); ipcMain.handle("redis:hashSet", async (_e, id, key, field, value) => { await setHashField(id, key, field, value); }); ipcMain.handle("redis:hashDel", async (_e, id, key, field) => { await deleteHashField(id, key, field); }); ipcMain.handle("redis:listRange", async (_e, id, key, start, stop) => { return listRange(id, key, start, stop); }); ipcMain.handle("redis:listLength", async (_e, id, key) => { return listLength(id, key); }); ipcMain.handle("redis:listPush", async (_e, id, key, values) => { return listPush(id, key, ...values); }); ipcMain.handle("redis:listSet", async (_e, id, key, index, value) => { await listSet(id, key, index, value); }); ipcMain.handle("redis:listRemove", async (_e, id, key, count, value) => { return listRemove(id, key, count, value); }); ipcMain.handle("redis:setMembers", async (_e, id, key) => { return setMembers(id, key); }); ipcMain.handle("redis:setSize", async (_e, id, key) => { return setSize(id, key); }); ipcMain.handle("redis:setAdd", async (_e, id, key, members) => { return setAdd(id, key, ...members); }); ipcMain.handle("redis:setRemove", async (_e, id, key, members) => { return setRemove(id, key, ...members); }); ipcMain.handle("redis:zsetRange", async (_e, id, key, start, stop, withScores) => { return zsetRange(id, key, start, stop, withScores); }); ipcMain.handle("redis:zsetSize", async (_e, id, key) => { return zsetSize(id, key); }); ipcMain.handle("redis:zsetAdd", async (_e, id, key, score, member) => { return zsetAdd(id, key, score, member); }); ipcMain.handle("redis:zsetRemove", async (_e, id, key, members) => { return zsetRemove(id, key, ...members); }); ipcMain.handle("redis:streamInfo", async (_e, id, key) => { return streamInfo(id, key); }); ipcMain.handle("redis:streamRange", async (_e, id, key, start, end, count) => { return streamRange(id, key, start, end, count); }); ipcMain.handle("redis:streamAdd", async (_e, id, key, streamId, fieldValues) => { return streamAdd(id, key, streamId, fieldValues); }); ipcMain.handle("redis:streamTrim", async (_e, id, key, maxLen) => { return streamTrim(id, key, maxLen); }); ipcMain.handle("redis:streamDel", async (_e, id, key, ids) => { return streamDel(id, key, ...ids); }); } let mainWindow = null; function getIconPath() { return join(__dirname, "../../build/icons/icon_256.png"); } function createWindow() { 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" }); 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(); });