feat: support value type ReJSON #152
This commit is contained in:
parent
094705e87d
commit
e92eb525e7
|
@ -591,7 +591,12 @@ func (b *browserService) GetKeyType(param types.KeySummaryParam) (resp types.JSR
|
|||
}
|
||||
|
||||
var data types.KeySummary
|
||||
data.Type = strings.ToLower(keyType)
|
||||
switch keyType {
|
||||
case "ReJSON-RL":
|
||||
data.Type = "JSON"
|
||||
default:
|
||||
data.Type = strings.ToLower(keyType)
|
||||
}
|
||||
|
||||
resp.Success = true
|
||||
resp.Data = data
|
||||
|
@ -624,7 +629,7 @@ func (b *browserService) GetKeySummary(param types.KeySummaryParam) (resp types.
|
|||
}
|
||||
size, _ := client.MemoryUsage(ctx, key, 0).Result()
|
||||
data := types.KeySummary{
|
||||
Type: strings.ToLower(typeVal.Val()),
|
||||
Type: typeVal.Val(),
|
||||
Size: size,
|
||||
}
|
||||
if data.Type == "none" {
|
||||
|
@ -655,6 +660,9 @@ func (b *browserService) GetKeySummary(param types.KeySummaryParam) (resp types.
|
|||
data.Length, err = client.ZCard(ctx, key).Result()
|
||||
case "stream":
|
||||
data.Length, err = client.XLen(ctx, key).Result()
|
||||
case "ReJSON-RL":
|
||||
data.Type = "JSON"
|
||||
data.Length = 0
|
||||
default:
|
||||
err = errors.New("unknown key type")
|
||||
}
|
||||
|
@ -1091,6 +1099,12 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
resp.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
case "rejson-rl":
|
||||
var jsonStr string
|
||||
data.KeyType = "JSON"
|
||||
jsonStr, err = client.JSONGet(ctx, key).Result()
|
||||
data.Value, data.Decode, data.Format = convutil.ConvertTo(jsonStr, types.DECODE_NONE, types.FORMAT_JSON, nil)
|
||||
}
|
||||
if err != nil {
|
||||
resp.Msg = err.Error()
|
||||
|
@ -1235,6 +1249,11 @@ func (b *browserService) SetKeyValue(param types.SetKeyParam) (resp types.JSResp
|
|||
}
|
||||
}
|
||||
}
|
||||
case "json":
|
||||
err = client.JSONSet(ctx, key, ".", param.Value).Err()
|
||||
if err == nil && expiration > 0 {
|
||||
client.Expire(ctx, key, expiration)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Copy from '@/components/icons/Copy.vue'
|
||||
import Save from '@/components/icons/Save.vue'
|
||||
import { useThemeVars } from 'naive-ui'
|
||||
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||
import { isEmpty, toLower } from 'lodash'
|
||||
import useBrowserStore from 'stores/browser.js'
|
||||
import { decodeRedisKey } from '@/utils/key_convert.js'
|
||||
import ContentEditor from '@/components/content_value/ContentEditor.vue'
|
||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||
import { formatBytes } from '@/utils/byte_convert.js'
|
||||
|
||||
const props = defineProps({
|
||||
name: String,
|
||||
db: Number,
|
||||
keyPath: String,
|
||||
keyCode: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
ttl: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
value: String,
|
||||
size: Number,
|
||||
length: Number,
|
||||
loading: Boolean,
|
||||
})
|
||||
|
||||
const i18n = useI18n()
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {ComputedRef<string|number[]>}
|
||||
*/
|
||||
const keyName = computed(() => {
|
||||
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
|
||||
})
|
||||
|
||||
const keyType = redisTypes.JSON
|
||||
|
||||
const editingContent = ref('')
|
||||
|
||||
const displayValue = computed(() => {
|
||||
if (props.loading) {
|
||||
return ''
|
||||
}
|
||||
return decodeRedisKey(props.value)
|
||||
})
|
||||
|
||||
const enableSave = computed(() => {
|
||||
return editingContent.value !== displayValue.value && !props.loading
|
||||
})
|
||||
|
||||
const showMemoryUsage = computed(() => {
|
||||
return !isNaN(props.size) && props.size > 0
|
||||
})
|
||||
|
||||
/**
|
||||
* Copy value
|
||||
*/
|
||||
const onCopyValue = () => {
|
||||
ClipboardSetText(displayValue.value)
|
||||
.then((succ) => {
|
||||
if (succ) {
|
||||
$message.success(i18n.t('interface.copy_succ'))
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
$message.error(e.message)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Save value
|
||||
*/
|
||||
const browserStore = useBrowserStore()
|
||||
const saving = ref(false)
|
||||
|
||||
const onInput = (content) => {
|
||||
editingContent.value = content
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
saving.value = true
|
||||
try {
|
||||
const { success, msg } = await browserStore.setKey({
|
||||
server: props.name,
|
||||
db: props.db,
|
||||
key: keyName.value,
|
||||
keyType: toLower(keyType),
|
||||
value: editingContent.value,
|
||||
ttl: -1,
|
||||
format: formatTypes.JSON,
|
||||
decode: decodeTypes.NONE,
|
||||
})
|
||||
if (success) {
|
||||
$message.success(i18n.t('interface.save_value_succ'))
|
||||
} else {
|
||||
$message.error(msg)
|
||||
}
|
||||
} catch (e) {
|
||||
$message.error(e.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reset: () => {
|
||||
editingContent.value = ''
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content-wrapper flex-box-v">
|
||||
<slot name="toolbar" />
|
||||
<div class="tb2 value-item-part flex-box-h">
|
||||
<div class="flex-item-expand"></div>
|
||||
<n-button-group>
|
||||
<n-button :disabled="saving" :focusable="false" @click="onCopyValue">
|
||||
<template #icon>
|
||||
<n-icon :component="Copy" size="18" />
|
||||
</template>
|
||||
{{ $t('interface.copy_value') }}
|
||||
</n-button>
|
||||
<n-button
|
||||
:disabled="!enableSave"
|
||||
:loading="saving"
|
||||
:secondary="enableSave"
|
||||
:type="enableSave ? 'primary' : ''"
|
||||
@click="onSave">
|
||||
<template #icon>
|
||||
<n-icon :component="Save" size="18" />
|
||||
</template>
|
||||
{{ $t('common.save') }}
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
</div>
|
||||
<div class="value-wrapper value-item-part flex-item-expand flex-box-v">
|
||||
<n-spin :show="props.loading" />
|
||||
<content-editor
|
||||
v-show="!props.loading"
|
||||
:content="displayValue"
|
||||
:loading="props.loading"
|
||||
class="flex-item-expand"
|
||||
language="json"
|
||||
style="height: 100%"
|
||||
@input="onInput"
|
||||
@reset="onInput"
|
||||
@save="onSave" />
|
||||
</div>
|
||||
<div class="value-footer flex-box-h">
|
||||
<n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
|
||||
<div class="flex-item-expand" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.value-wrapper {
|
||||
//overflow: hidden;
|
||||
border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.value-footer {
|
||||
border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||
background-color: v-bind('themeVars.tableHeaderColor');
|
||||
}
|
||||
</style>
|
|
@ -14,6 +14,7 @@ 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'
|
||||
|
||||
const themeVars = useThemeVars()
|
||||
const browserStore = useBrowserStore()
|
||||
|
@ -66,6 +67,7 @@ const valueComponents = {
|
|||
[redisTypes.SET]: ContentValueSet,
|
||||
[redisTypes.ZSET]: ContentValueZset,
|
||||
[redisTypes.STREAM]: ContentValueStream,
|
||||
[redisTypes.JSON]: ContentValueJson,
|
||||
}
|
||||
|
||||
const keyName = computed(() => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import useTabStore from 'stores/tab.js'
|
|||
import NewStreamValue from '@/components/new_value/NewStreamValue.vue'
|
||||
import useBrowserStore from 'stores/browser.js'
|
||||
import Import from '@/components/icons/Import.vue'
|
||||
import NewJsonValue from '@/components/new_value/NewJsonValue.vue'
|
||||
|
||||
const i18n = useI18n()
|
||||
const newForm = reactive({
|
||||
|
@ -54,6 +55,7 @@ const newValueComponent = {
|
|||
[types.SET]: NewSetValue,
|
||||
[types.ZSET]: NewZSetValue,
|
||||
[types.STREAM]: NewStreamValue,
|
||||
[types.JSON]: NewJsonValue,
|
||||
}
|
||||
const defaultValue = {
|
||||
[types.STRING]: '',
|
||||
|
@ -62,6 +64,7 @@ const defaultValue = {
|
|||
[types.SET]: [],
|
||||
[types.ZSET]: [],
|
||||
[types.STREAM]: [],
|
||||
[types.JSON]: '{}',
|
||||
}
|
||||
|
||||
const dialogStore = useDialog()
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<script setup>
|
||||
const props = defineProps({
|
||||
value: String,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:value'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-form-item :label="$t('common.value')">
|
||||
<n-input
|
||||
:rows="6"
|
||||
:value="props.value"
|
||||
placeholder=""
|
||||
type="textarea"
|
||||
@input="(val) => emit('update:value', val)" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -9,6 +9,7 @@ export const types = {
|
|||
SET: 'SET',
|
||||
ZSET: 'ZSET',
|
||||
STREAM: 'STREAM',
|
||||
JSON: 'JSON',
|
||||
}
|
||||
|
||||
export const typesShortName = {
|
||||
|
@ -18,6 +19,7 @@ export const typesShortName = {
|
|||
SET: 'E',
|
||||
ZSET: 'Z',
|
||||
STREAM: 'X',
|
||||
JSON: 'J',
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,6 +33,7 @@ export const typesColor = {
|
|||
[types.SET]: '#F59E0B',
|
||||
[types.ZSET]: '#EF4444',
|
||||
[types.STREAM]: '#EC4899',
|
||||
[types.JSON]: '#374254',
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,6 +47,7 @@ export const typesBgColor = {
|
|||
[types.SET]: '#FDF1DF',
|
||||
[types.ZSET]: '#FAEAED',
|
||||
[types.STREAM]: '#FDE6F1',
|
||||
[types.JSON]: '#C1C1D3',
|
||||
}
|
||||
|
||||
// export const typesName = Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.name]))
|
||||
|
|
Loading…
Reference in New Issue