import { create } from 'zustand' import { request } from './socket.service' import { useCommonStore } from './common.store' import { useRedisStateStore } from './redis-state.store' import { useSettingsStore } from './settings.store' import { useI18nStore } from './i18n.store' import { useOverlayStore } from './overlay.store' import { navigateTo } from './navigation.store' import { parseRedisInfo } from './redis-parser' // --- Event bus for component communication --- type Callback = (...args: any[]) => void type VoidCallback = () => void const cmdListeners: Record> = {} export function onCommandEvent(event: string, cb: Callback): VoidCallback { if (!cmdListeners[event]) cmdListeners[event] = new Set() cmdListeners[event].add(cb) return () => { cmdListeners[event].delete(cb) } } export function emitCommand(event: string, data?: any) { cmdListeners[event]?.forEach(cb => cb(data)) } interface MainCommandState { connect: (conn: any) => Promise selectDatabase: (dbIndex: number) => Promise save: () => Promise refresh: (options?: { withoutParent?: boolean; force?: boolean }) => Promise statistics: () => Promise disconnect: () => Promise addKey: (options: { event: Event; node?: any }) => void getCurrentDatabase: () => number setCurrentDatabase: (value: number) => void } let lastRefreshAt = 0 export const useMainCommandStore = create(() => ({ connect: async (conn: any) => { const cloned = structuredClone(conn) const settings = useSettingsStore.getState() const overlay = useOverlayStore.getState() try { const dbStorageKey = settings.getStorageKeyCurrentDatabase(cloned.id) let db: string | undefined try { db = localStorage.getItem(dbStorageKey) ?? undefined } catch {} if (cloned.askAuth === true) { try { const auth = await useCommonStore.getState().askAuth() cloned.username = auth.username || undefined cloned.password = auth.password || undefined } catch { return } } const strings = useI18nStore.getState().strings overlay.show({ message: strings?.title?.connectingRedis }) useRedisStateStore.setState({ connectionState: 'connecting' }) const response = await request({ action: 'connection/connect', payload: { connection: cloned, db }, }) const databaseIndexes: number[] = [] let i = 0 while (i < response.databases) databaseIndexes.push(i++) const commands: string[] = [] Object.keys(response.commands ?? {}).forEach(k => commands.push(response.commands[k][0])) commands.sort() const modules = Array.isArray(response.modules) ? response.modules : [] useRedisStateStore.setState({ page: 1, monitor: false, dbsize: response.dbsize, databaseIndexes, connection: cloned, commands, commandsMeta: response.commandsMeta ?? {}, modules, hasReJSON: modules.some((m: any) => m.name === 'ReJSON'), hasRediSearch: modules.some((m: any) => m.name === 'search'), hasTimeSeries: modules.some((m: any) => m.name === 'timeseries' || m.name === 'Timeseries'), hasBloom: modules.some((m: any) => m.name === 'bf'), connectionState: 'connected', }) useCommonStore.getState().loadRedisInfoResponse({ response }) try { localStorage.setItem(settings.connectInfoStorageKey, JSON.stringify(cloned)) } catch {} } catch (error) { try { localStorage.removeItem(settings.connectInfoStorageKey) } catch {} useRedisStateStore.setState({ connection: undefined, connectionState: 'none' }) useCommonStore.getState().generalHandleError(error) } finally { overlay.hide() } }, selectDatabase: async (dbIndex: number) => { const state = useRedisStateStore.getState() const settings = useSettingsStore.getState() useRedisStateStore.setState({ currentDatabase: dbIndex, page: 1, redisChanged: true }) try { await request({ action: 'redis/console', payload: { command: `select ${dbIndex}` } }) const strings = useI18nStore.getState().strings const fn = strings?.status?.dbChanged const msg = typeof fn === 'function' ? fn({ db: dbIndex }) : '' if (msg) useCommonStore.getState().toast(msg) // Persist const storageKey = settings.getStorageKeyCurrentDatabase(state.connection?.id) if (storageKey) try { localStorage.setItem(storageKey, String(dbIndex)) } catch {} await useMainCommandStore.getState().refresh({ force: true }) } catch (e) { useCommonStore.getState().generalHandleError(e) } }, save: async () => { try { const response = await request({ action: 'redis/save' }) const info = parseRedisInfo(response.info) useRedisStateStore.setState({ info }) const strings = useI18nStore.getState().strings useCommonStore.getState().toast(strings?.status?.savedRedis) } catch (e) { useCommonStore.getState().generalHandleError(e) } }, statistics: async () => { try { navigateTo('database.statistics') await useMainCommandStore.getState().refresh({ force: true }) } catch (e) { useCommonStore.getState().generalHandleError(e) } }, refresh: async (options: { withoutParent?: boolean; force?: boolean } = {}) => { const now = Date.now() if (!options.force && now - lastRefreshAt < 2000) return lastRefreshAt = now try { const state = useRedisStateStore.getState() const settings = useSettingsStore.getState() const payload: any = {} const searchValue = state.search if (!settings.searchClientSide && typeof searchValue === 'string' && searchValue.length > 0) { payload.match = settings.searchStartsWith ? searchValue + '*' : '*' + searchValue + '*' } const response = await request({ action: 'redis/refresh', payload }) useRedisStateStore.setState({ dbsize: response.dbsize, redisChanged: true }) useCommonStore.getState().loadRedisInfoResponse({ response }) emitCommand('tree-refresh') if (!options.withoutParent) emitCommand('refresh-key') } catch (e) { useCommonStore.getState().generalHandleError(e) } }, disconnect: async () => { const state = useRedisStateStore.getState() const settings = useSettingsStore.getState() try { localStorage.removeItem(settings.connectInfoStorageKey) } catch {} useRedisStateStore.setState({ connection: undefined, connectionState: 'none', redisConnections: {}, monitor: false, }) try { await request({ action: 'connection/disconnect', payload: { connectionId: state.connection?.id }, }) } catch {} navigateTo('settings') }, addKey: (options: { event: Event; node?: any }) => { options.event.stopPropagation() emitCommand('key-new', options) }, getCurrentDatabase: () => { const state = useRedisStateStore.getState() const settings = useSettingsStore.getState() let db: any = state.currentDatabase if (db === undefined) { const storageKey = settings.getStorageKeyCurrentDatabase(state.connection?.id) if (storageKey) try { db = localStorage.getItem(storageKey) } catch {} } return Number(db ?? 0) }, setCurrentDatabase: (value: number) => { const state = useRedisStateStore.getState() const settings = useSettingsStore.getState() useRedisStateStore.setState({ currentDatabase: value }) const storageKey = settings.getStorageKeyCurrentDatabase(state.connection?.id) if (storageKey) try { localStorage.setItem(storageKey, String(value)) } catch {} }, }))