diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go index ccfd41a..2c1944f 100644 --- a/backend/services/browser_service.go +++ b/backend/services/browser_service.go @@ -756,7 +756,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS var str string str, err = client.Get(ctx, key).Result() data.Value = strutil.EncodeRedisKey(str) - //data.Value, data.Decode, data.Format = strutil.ConvertTo(str, param.Decode, param.Format) + //data.Value, data.Decode, data.Format = convutil.ConvertTo(str, param.Decode, param.Format, decoder) case "list": loadListHandle := func() ([]types.ListEntryItem, bool, bool, error) { diff --git a/frontend/src/components/content/ContentPane.vue b/frontend/src/components/content/ContentPane.vue index 9834fb3..18773b2 100644 --- a/frontend/src/components/content/ContentPane.vue +++ b/frontend/src/components/content/ContentPane.vue @@ -13,7 +13,6 @@ import ContentValueWrapper from '@/components/content_value/ContentValueWrapper. import ContentCli from '@/components/content_value/ContentCli.vue' import Monitor from '@/components/icons/Monitor.vue' import ContentSlog from '@/components/content_value/ContentSlog.vue' -import { decodeTypes, formatTypes } from '@/consts/value_view_type.js' import ContentMonitor from '@/components/content_value/ContentMonitor.vue' import { decodeRedisKey } from '@/utils/key_convert.js' import ContentPubsub from '@/components/content_value/ContentPubsub.vue' @@ -58,8 +57,8 @@ const tabContent = computed(() => { value: tab.value, size: tab.size || 0, length: tab.length || 0, - decode: tab.decode || decodeTypes.NONE, - format: tab.format || formatTypes.RAW, + decode: tab.decode, + format: tab.format, matchPattern: tab.matchPattern || '', end: tab.end === true, loading: tab.loading === true, diff --git a/frontend/src/components/content_value/ContentEntryEditor.vue b/frontend/src/components/content_value/ContentEntryEditor.vue index ce56563..00de864 100644 --- a/frontend/src/components/content_value/ContentEntryEditor.vue +++ b/frontend/src/components/content_value/ContentEntryEditor.vue @@ -174,8 +174,8 @@ const onSave = () => { { format: retFormat, } = await browserStore.convertValue({ value: props.value, - decode, - format, + decode: decode || props.decode, + format: format || props.format, }) editingContent.value = viewAs.value = value viewAs.decode = decode || retDecode viewAs.format = format || retFormat + browserStore.setSelectedFormat(props.name, props.keyPath, props.db, viewAs.format, viewAs.decode) } finally { converting.value = false } @@ -205,8 +212,8 @@ defineExpose({ :language="viewLanguage" :loading="props.loading" :offset-key="props.keyPath" - keep-offset class="flex-item-expand" + keep-offset style="height: 100%" @input="onInput" @reset="onInput" diff --git a/frontend/src/components/content_value/ContentValueWrapper.vue b/frontend/src/components/content_value/ContentValueWrapper.vue index 80185fb..008beeb 100644 --- a/frontend/src/components/content_value/ContentValueWrapper.vue +++ b/frontend/src/components/content_value/ContentValueWrapper.vue @@ -11,7 +11,6 @@ import useBrowserStore from 'stores/browser.js' import { computed, onMounted, ref, watch } from 'vue' import { isEmpty } from 'lodash' import useDialogStore from 'stores/dialog.js' -import { decodeTypes, formatTypes } from '@/consts/value_view_type.js' import { useI18n } from 'vue-i18n' import ContentToolbar from '@/components/content_value/ContentToolbar.vue' import ContentValueJson from '@/components/content_value/ContentValueJson.vue' @@ -86,15 +85,15 @@ const loadData = async (reset, full, selMatch) => { if (!!props.blank) { return } - const { name, db, matchPattern, decode, format } = data.value + const { name, db, matchPattern } = data.value reset = reset === true await browserStore.loadKeyDetail({ server: name, db: db, key: keyName.value, matchPattern: selMatch === undefined ? matchPattern : selMatch, - decode: reset ? decodeTypes.NONE : decode, - format: reset ? formatTypes.RAW : format, + decode: '', + format: '', reset, full: full === true, }) @@ -111,12 +110,15 @@ const loadData = async (reset, full, selMatch) => { const onReload = async (selDecode, selFormat) => { try { const { name, db, keyCode, keyPath, decode, format, matchPattern } = data.value + const targetFormat = selFormat || format + const targetDecode = selDecode || decode + browserStore.setSelectedFormat(name, keyPath, db, targetFormat, targetDecode) await browserStore.reloadKey({ server: name, db, key: keyCode || keyPath, - decode: selDecode || decode, - format: selFormat || format, + decode: targetDecode, + format: targetFormat, matchPattern, }) } finally { diff --git a/frontend/src/objects/redisServerState.js b/frontend/src/objects/redisServerState.js index 2f64a71..e75991a 100644 --- a/frontend/src/objects/redisServerState.js +++ b/frontend/src/objects/redisServerState.js @@ -1,4 +1,4 @@ -import { initial, isEmpty, join, last, mapValues, size, slice, sortBy, split, toUpper } from 'lodash' +import { get, initial, isEmpty, join, last, mapValues, size, slice, sortBy, split, toUpper } from 'lodash' import useConnectionStore from 'stores/connections.js' import { ConnectionType } from '@/consts/connection_type.js' import { RedisDatabaseItem } from '@/objects/redisDatabaseItem.js' @@ -49,6 +49,8 @@ export class RedisServerState { this.loadingState = loadingState this.viewType = viewType this.nodeMap = nodeMap + this.decodeHistory = new Map() + this.decodeHistoryLimit = 100 this.getRoot() const connStore = useConnectionStore() @@ -457,4 +459,37 @@ export class RedisServerState { this.patternFilter = pattern === null ? this.patternFilter : pattern this.typeFilter = type === null ? this.typeFilter : type } + + /** + * add manually selected decode type to history + * @param {string} key + * @param {number} db + * @param {string} format + * @param {string} decode + */ + addDecodeHistory(key, db, format = '', decode = '') { + const decodeKey = `${key}#${db}` + this.decodeHistory.delete(decodeKey) + if (isEmpty(format) && isEmpty(decode)) { + // reset to default, remove from history + return + } + + this.decodeHistory.set(decodeKey, [format, decode]) + while (this.decodeHistory.size > this.decodeHistoryLimit) { + const k = this.decodeHistory.keys().next().value + this.decodeHistory.delete(k) + } + } + + /** + * get manually selected decode type from history + * @param {string|number[]} key + * @param {number} db + * @return {[]} + */ + getDecodeHistory(key, db) { + const h = this.decodeHistory.get(`${nativeRedisKey(key)}#${db}`) || [] + return [get(h, 0, ''), get(h, 1, '')] + } } diff --git a/frontend/src/stores/browser.js b/frontend/src/stores/browser.js index 3bcbff9..b47e562 100644 --- a/frontend/src/stores/browser.js +++ b/frontend/src/stores/browser.js @@ -422,7 +422,7 @@ const useBrowserStore = defineStore('browser', { clearValue, }) } catch (e) { - $message.error('') + $message.error(e.message || 'unknown error') } finally { } }, @@ -495,14 +495,19 @@ const useBrowserStore = defineStore('browser', { */ async loadKeyDetail({ server, db, key, format, decode, matchPattern, reset, full }) { const tab = useTabStore() + const serverInst = this.servers[server] + if (serverInst == null) { + return + } try { tab.updateLoading({ server, db, loading: true }) + const [storeFormat, storeDecode] = serverInst.getDecodeHistory(key, db) const { data, success, msg } = await GetKeyDetail({ server, db, key, - format, - decode, + format: isEmpty(format) ? storeFormat : format, + decode: isEmpty(decode) ? storeDecode : decode, matchPattern, full: full === true, reset, @@ -523,8 +528,8 @@ const useBrowserStore = defineStore('browser', { db, key: nativeRedisKey(key), value, - decode: retDecode, - format: retFormat, + decode: retDecode || storeDecode, + format: retFormat || storeFormat, reset: retReset, matchPattern: retMatch || '', end, @@ -1935,6 +1940,22 @@ const useBrowserStore = defineStore('browser', { serverInst.setFilter({ pattern, type }) } }, + + /** + * + * @param {string} server + * @param {string} key + * @param {number} db + * @param {string} format + * @param {string} decode + */ + setSelectedFormat(server, key, db, format, decode) { + const serverInst = this.servers[server] + if (serverInst == null) { + return + } + serverInst.addDecodeHistory(key, db, format, decode) + }, }, }) diff --git a/frontend/src/stores/tab.js b/frontend/src/stores/tab.js index 1927a4f..f54f45b 100644 --- a/frontend/src/stores/tab.js +++ b/frontend/src/stores/tab.js @@ -145,9 +145,25 @@ const useTabStore = defineStore('tab', { * @param {number} [length] * @param {string} [matchPattern] * @param {boolean} [clearValue] + * @param {string} format + * @param {string} decode * @param {*} [value] */ - upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, length, matchPattern = '', clearValue }) { + upsertTab({ + subTab, + server, + db, + type, + ttl, + key, + keyCode, + size, + length, + matchPattern = '', + clearValue, + format = '', + decode = '', + }) { let tabIndex = findIndex(this.tabList, { name: server }) if (tabIndex === -1) { const tabItem = new TabItem({ @@ -164,6 +180,8 @@ const useTabStore = defineStore('tab', { length, matchPattern, value: undefined, + format, + decode, }) this.tabList.push(tabItem) tabIndex = this.tabList.length - 1 @@ -182,6 +200,8 @@ const useTabStore = defineStore('tab', { tab.size = size tab.length = length tab.matchPattern = matchPattern + tab.format = format + tab.decode = decode if (clearValue === true) { tab.value = undefined }