diff --git a/backend/services/browser_service.go b/backend/services/browser_service.go index 737dd8b..103e3a3 100644 --- a/backend/services/browser_service.go +++ b/backend/services/browser_service.go @@ -630,7 +630,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.DecodeType, data.ViewAs = strutil.ConvertTo(str, param.DecodeType, param.ViewAs) + //data.Value, data.Decode, data.Format = strutil.ConvertTo(str, param.Decode, param.Format) case "list": loadListHandle := func() ([]string, bool, error) { @@ -659,11 +659,13 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS data.Value, data.End, err = loadListHandle() case "hash": - loadHashHandle := func() (map[string]string, bool, error) { - items := map[string]string{} + loadHashHandle := func() ([]types.HashEntryItem, bool, error) { + //items := map[string]string{} scanSize := int64(Preferences().GetScanSize()) + items := make([]types.HashEntryItem, 0, scanSize) var loadedVal []string var cursor uint64 + var doConvert = len(param.Decode) > 0 && len(param.Format) > 0 if param.Full { // load all cursor = 0 @@ -672,8 +674,18 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS if err != nil { return nil, false, err } + var v string for i := 0; i < len(loadedVal); i += 2 { - items[loadedVal[i]] = loadedVal[i+1] + if doConvert { + v, _, _ = strutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format) + } else { + v = loadedVal[i+1] + } + items = append(items, types.HashEntryItem{ + Key: loadedVal[i], + Value: strutil.EncodeRedisKey(loadedVal[i+1]), + DisplayValue: v, + }) } if cursor == 0 { break @@ -685,8 +697,18 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS if err != nil { return nil, false, err } + var v string for i := 0; i < len(loadedVal); i += 2 { - items[loadedVal[i]] = loadedVal[i+1] + if doConvert { + v, _, _ = strutil.ConvertTo(loadedVal[i+1], param.Decode, param.Format) + } else { + v = loadedVal[i+1] + } + items = append(items, types.HashEntryItem{ + Key: loadedVal[i], + Value: strutil.EncodeRedisKey(loadedVal[i+1]), + DisplayValue: v, + }) } } setEntryCursor(cursor) @@ -694,6 +716,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS } data.Value, data.End, err = loadHashHandle() + data.Decode, data.Format = param.Decode, param.Format if err != nil { resp.Msg = err.Error() return @@ -841,8 +864,8 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS } // ConvertValue convert value with decode method and format -// blank decodeType indicate auto decode -// blank viewAs indicate auto format +// blank decode indicate auto decode +// blank format indicate auto format func (b *browserService) ConvertValue(value any, decode, format string) (resp types.JSResp) { str := strutil.DecodeRedisKey(value) value, decode, format = strutil.ConvertTo(str, decode, format) diff --git a/backend/types/js_resp.go b/backend/types/js_resp.go index 7b54681..de9ab65 100644 --- a/backend/types/js_resp.go +++ b/backend/types/js_resp.go @@ -23,19 +23,25 @@ type KeyDetailParam struct { Server string `json:"server"` DB int `json:"db"` Key any `json:"key"` - ViewAs string `json:"viewAs,omitempty"` - DecodeType string `json:"decodeType,omitempty"` + Format string `json:"format,omitempty"` + Decode string `json:"decode,omitempty"` MatchPattern string `json:"matchPattern,omitempty"` Reset bool `json:"reset"` Full bool `json:"full"` } +type HashEntryItem struct { + Key string `json:"k"` + Value any `json:"v"` + DisplayValue string `json:"dv"` +} + type KeyDetail struct { - Value any `json:"value"` - Length int64 `json:"length,omitempty"` - ViewAs string `json:"viewAs,omitempty"` - DecodeType string `json:"decodeType,omitempty"` - End bool `json:"end"` + Value any `json:"value"` + Length int64 `json:"length,omitempty"` + Format string `json:"format,omitempty"` + Decode string `json:"decode,omitempty"` + End bool `json:"end"` } type SetKeyParam struct { diff --git a/frontend/src/components/content/ContentPane.vue b/frontend/src/components/content/ContentPane.vue index d49f7f4..9440ebf 100644 --- a/frontend/src/components/content/ContentPane.vue +++ b/frontend/src/components/content/ContentPane.vue @@ -14,6 +14,7 @@ import ContentCli from '@/components/content_value/ContentCli.vue' import Monitor from '@/components/icons/Monitor.vue' import Pub from '@/components/icons/Pub.vue' import ContentSlog from '@/components/content_value/ContentSlog.vue' +import { decodeTypes, formatTypes } from '@/consts/value_view_type.js' const themeVars = useThemeVars() @@ -54,9 +55,9 @@ const tabContent = computed(() => { value: tab.value, size: tab.size || 0, length: tab.length || 0, - viewAs: tab.viewAs, - decode: tab.decode, - end: tab.end, + decode: tab.decode || decodeTypes.NONE, + format: tab.format || formatTypes.PLAIN_TEXT, + 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 9bdf364..bcb5b7e 100644 --- a/frontend/src/components/content_value/ContentEntryEditor.vue +++ b/frontend/src/components/content_value/ContentEntryEditor.vue @@ -30,7 +30,7 @@ const props = defineProps({ const themeVars = useThemeVars() const browserStore = useBrowserStore() -const emit = defineEmits(['update:field', 'update:value', 'update:decode', 'update:format', 'viewAs', 'save', 'cancel']) +const emit = defineEmits(['update:field', 'update:value', 'update:decode', 'update:format', 'save', 'cancel']) const model = reactive({ field: '', value: '', @@ -89,14 +89,11 @@ const onFormatChanged = async (decode = '', format = '') => { } const onUpdateValue = (value) => { - // TODO: reconvert back to origin format // emit('update:value', value) viewAs.value = value } const onSave = () => { - // TODO: convert value by decode and format - // viewAs.decode, viewAs.format emit('save', viewAs.field, viewAs.value, viewAs.decode, viewAs.format) } diff --git a/frontend/src/components/content_value/ContentValueHash.vue b/frontend/src/components/content_value/ContentValueHash.vue index 0d75b50..c48e9b9 100644 --- a/frontend/src/components/content_value/ContentValueHash.vue +++ b/frontend/src/components/content_value/ContentValueHash.vue @@ -16,6 +16,8 @@ import LoadAll from '@/components/icons/LoadAll.vue' import IconButton from '@/components/common/IconButton.vue' import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue' import Edit from '@/components/icons/Edit.vue' +import FormatSelector from '@/components/content_value/FormatSelector.vue' +import { decodeRedisKey } from '@/utils/key_convert.js' const i18n = useI18n() const themeVars = useThemeVars() @@ -32,7 +34,10 @@ const props = defineProps({ type: Number, default: -1, }, - value: Object, + value: { + type: Array, + default: () => [], + }, size: Number, length: Number, format: { @@ -72,7 +77,7 @@ const filterType = ref(1) const browserStore = useBrowserStore() const dialogStore = useDialogStore() const keyType = redisTypes.HASH -const currentEditRow = ref({ +const currentEditRow = reactive({ no: 0, key: '', value: null, @@ -81,7 +86,7 @@ const currentEditRow = ref({ }) const inEdit = computed(() => { - return currentEditRow.value.no > 0 + return currentEditRow.no > 0 }) const tableRef = ref(null) const fieldColumn = reactive({ @@ -95,7 +100,10 @@ const fieldColumn = reactive({ }, filterOptionValue: null, filter(value, row) { - return !!~row.key.indexOf(value.toString()) + return !!~row.k.indexOf(value.toString()) + }, + render(row) { + return decodeRedisKey(row.k) }, }) const valueColumn = reactive({ @@ -111,17 +119,20 @@ const valueColumn = reactive({ filter(value, row) { return !!~row.value.indexOf(value.toString()) }, + render(row) { + return row.dv + }, }) const startEdit = async ({ no, key, value }) => { - currentEditRow.value.value = value - currentEditRow.value.no = no - currentEditRow.value.key = key + currentEditRow.value = value + currentEditRow.no = no + currentEditRow.key = key } const saveEdit = async (field, value, decode, format) => { try { - const row = tableData.value[currentEditRow.value.no - 1] + const row = props.value[currentEditRow.no - 1] if (row == null) { throw new Error('row not exists') } @@ -130,15 +141,21 @@ const saveEdit = async (field, value, decode, format) => { server: props.name, db: props.db, key: keyName.value, - field: row.key, + field: row.k, newField: field, value, decode, format, }) if (success) { - row.key = field - row.value = updated[row.key] || '' + row.k = field + row.v = updated[row.k] || '' + const { value: displayVal } = await browserStore.convertValue({ + value: row.v, + decode: props.decode, + format: props.format, + }) + row.dv = displayVal $message.success(i18n.t('dialogue.save_value_succ')) } else { $message.error(msg) @@ -151,11 +168,11 @@ const saveEdit = async (field, value, decode, format) => { } const resetEdit = () => { - currentEditRow.value.no = 0 - currentEditRow.value.key = '' - currentEditRow.value.value = null - currentEditRow.value.format = formatTypes.PLAIN_TEXT - currentEditRow.value.decode = decodeTypes.NONE + currentEditRow.no = 0 + currentEditRow.key = '' + currentEditRow.value = null + currentEditRow.format = formatTypes.PLAIN_TEXT + currentEditRow.decode = decodeTypes.NONE } const actionColumn = { @@ -165,21 +182,21 @@ const actionColumn = { align: 'center', titleAlign: 'center', fixed: 'right', - render: (row) => { + render: (row, index) => { return h(EditableTableColumn, { editing: false, - bindKey: row.key, - onEdit: () => startEdit(row), + bindKey: row.k, + onEdit: () => startEdit({ no: index + 1, key: row.k, value: row.v }), onDelete: async () => { try { const { success, msg } = await browserStore.removeHashField( props.name, props.db, keyName.value, - row.key, + row.k, ) if (success) { - $message.success(i18n.t('dialogue.delete_key_succ', { key: row.key })) + $message.success(i18n.t('dialogue.delete_key_succ', { key: row.k })) } else { $message.error(msg) } @@ -200,6 +217,9 @@ const columns = computed(() => { width: 80, align: 'center', titleAlign: 'center', + render(row, index) { + return index + 1 + }, }, fieldColumn, valueColumn, @@ -213,12 +233,12 @@ const columns = computed(() => { width: 80, align: 'center', titleAlign: 'center', - render(row) { - if (row.no === currentEditRow.value.no) { + render(row, index) { + if (index + 1 === currentEditRow.no) { // editing row, show edit state return h(NIcon, { size: 16, color: 'red' }, () => h(Edit, { strokeWidth: 5 })) } else { - return row.no + return index + 1 } }, }, @@ -227,32 +247,19 @@ const columns = computed(() => { } }) -const tableData = computed(() => { - const data = [] - let index = 0 - for (const key in props.value) { - data.push({ - no: ++index, - key, - value: props.value[key], - }) - } - return data -}) - -const rowProps = (row) => { +const rowProps = (row, index) => { return { onClick: () => { // in edit mode, switch edit row by click if (inEdit.value) { - startEdit(row) + startEdit({ no: index + 1, key: row.k, value: row.v }) } }, } } const entries = computed(() => { - const len = size(tableData.value) + const len = size(props.value) return `${len} / ${Math.max(len, props.length)}` }) @@ -296,6 +303,10 @@ const onUpdateFilter = (filters, sourceColumn) => { } } +const onFormatChanged = (selDecode, selFormat) => { + emit('reload', selDecode, selFormat) +} + defineExpose({ reset: () => { clearFilter() @@ -367,7 +378,7 @@ defineExpose({ :bordered="false" :bottom-bordered="false" :columns="columns" - :data="tableData" + :data="props.value" :loading="props.loading" :row-props="rowProps" :single-column="true" @@ -398,6 +409,12 @@ defineExpose({ {{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}
+ diff --git a/frontend/src/components/content_value/ContentValueWrapper.vue b/frontend/src/components/content_value/ContentValueWrapper.vue index b6766f0..4cfd9e5 100644 --- a/frontend/src/components/content_value/ContentValueWrapper.vue +++ b/frontend/src/components/content_value/ContentValueWrapper.vue @@ -11,6 +11,7 @@ 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' const themeVars = useThemeVars() const browserStore = useBrowserStore() @@ -36,7 +37,7 @@ const props = defineProps({ * value: [String, Object], * size: Number, * length: Number, - * viewAs: String, + * format: String, * decode: String, * end: Boolean * }>} @@ -65,7 +66,8 @@ const keyName = computed(() => { const loadData = async (reset, full) => { try { - const { name, db, view, decodeType, matchPattern } = data.value + const { name, db, view, decodeType, matchPattern, decode, format } = data.value + reset = reset === true await browserStore.loadKeyDetail({ server: name, db: db, @@ -73,17 +75,31 @@ const loadData = async (reset, full) => { viewType: view, decodeType: decodeType, matchPattern: matchPattern, - reset: reset === true, + decode: reset ? decodeTypes.NONE : decode, + format: reset ? formatTypes.PLAIN_TEXT : format, + reset, full: full === true, }) } finally { } } -const onReload = async () => { +/** + * reload current key + * @param {string} [selDecode] + * @param {string} [selFormat] + * @return {Promise} + */ +const onReload = async (selDecode, selFormat) => { try { - const { name, db, keyCode, keyPath } = data.value - await browserStore.reloadKey({ server: name, db, key: keyCode || keyPath }) + const { name, db, keyCode, keyPath, decode, format } = data.value + await browserStore.reloadKey({ + server: name, + db, + key: keyCode || keyPath, + decode: selDecode || decode, + format: selFormat || format, + }) } finally { } } @@ -116,6 +132,7 @@ const onLoadAll = () => { loadData(false, true) } +const contentRef = ref(null) const initContent = async () => { // onReload() try { @@ -123,7 +140,7 @@ const initContent = async () => { if (contentRef.value?.reset != null) { contentRef.value?.reset() } - await loadData(false, false) + await loadData(true, false) if (contentRef.value?.beforeShow != null) { await contentRef.value?.beforeShow() } @@ -137,7 +154,6 @@ onMounted(() => { initContent() }) -const contentRef = ref(null) watch(() => data.value?.keyPath, initContent) @@ -152,7 +168,9 @@ watch(() => data.value?.keyPath, initContent) :is="valueComponents[data.type]" ref="contentRef" :db="data.db" + :decode="data.decode" :end="data.end" + :format="data.format" :key-code="data.keyCode" :key-path="data.keyPath" :length="data.length" diff --git a/frontend/src/stores/browser.js b/frontend/src/stores/browser.js index d1e9e9c..1dd63ec 100644 --- a/frontend/src/stores/browser.js +++ b/frontend/src/stores/browser.js @@ -423,17 +423,19 @@ const useBrowserStore = defineStore('browser', { /** * reload key - * @param server - * @param db - * @param key + * @param {string} server + * @param {number} db + * @param {string|number[]} key + * @param {string} [decode] + * @param {string} [format] * @return {Promise} */ - async reloadKey({ server, db, key }) { + async reloadKey({ server, db, key, decode, format }) { const tab = useTabStore() try { tab.updateLoading({ server, db, loading: true }) await this.loadKeySummary({ server, db, key }) - await this.loadKeyDetail({ server, db, key, reset: true }) + await this.loadKeyDetail({ server, db, key, decode, format, reset: true }) } finally { tab.updateLoading({ server, db, loading: false }) } @@ -444,14 +446,14 @@ const useBrowserStore = defineStore('browser', { * @param {string} server * @param {number} db * @param {string|number[]} key - * @param {string} [viewType] - * @param {string} [decodeType] + * @param {string} [format] + * @param {string} [decode] * @param {string} [matchPattern] * @param {boolean} [reset] * @param {boolean} [full] * @return {Promise} */ - async loadKeyDetail({ server, db, key, viewType, decodeType, matchPattern, reset, full }) { + async loadKeyDetail({ server, db, key, format, decode, matchPattern, reset, full }) { const tab = useTabStore() try { tab.updateLoading({ server, db, loading: true }) @@ -459,25 +461,27 @@ const useBrowserStore = defineStore('browser', { server, db, key, - viewAs: viewType, - decodeType, + format, + decode, matchPattern, full: full === true, reset, lite: true, }) if (success) { - const { value, viewAs, decodeType: decode, end } = data + const { value, decode: retDecode, format: retFormat, end } = data tab.updateValue({ server, db, key: decodeRedisKey(key), value, - viewAs, - decode, + decode: retDecode, + format: retFormat, reset: reset || full === true, end, }) + } else { + $message.error('load key detail fail:' + msg) } } finally { tab.updateLoading({ server, db, loading: false }) diff --git a/frontend/src/stores/tab.js b/frontend/src/stores/tab.js index 73429b7..4b648d9 100644 --- a/frontend/src/stores/tab.js +++ b/frontend/src/stores/tab.js @@ -19,8 +19,8 @@ const useTabStore = defineStore('tab', { * @param {number} [size] memory usage * @param {number} [length] length of content or entries * @property {int} [ttl] ttl of current key - * @param {string} [viewAs] * @param {string} [decode] + * @param {string} [format] * @param {boolean} [end] * @param {boolean} [loading] */ @@ -149,18 +149,18 @@ const useTabStore = defineStore('tab', { * @param {number} db * @param {string} key * @param {*} value - * @param {string} [viewAs] + * @param {string} [format] * @param {string] [decode] * @param {boolean} reset * @param {boolean} [end] keep end status if not set */ - updateValue({ server, db, key, value, viewAs, decode, reset, end }) { + updateValue({ server, db, key, value, format, decode, reset, end }) { const tab = find(this.tabList, { name: server, db, key }) if (tab == null) { return } - tab.viewAs = viewAs || tab.viewAs + tab.format = format || tab.format tab.decode = decode || tab.decode if (typeof end === 'boolean') { tab.end = end