perf: support display binary key name which unreadable(convert to hex string) #49

This commit is contained in:
tiny-craft 2023-10-11 01:15:23 +08:00
parent a6645e3340
commit a4412d21d4
24 changed files with 485 additions and 145 deletions

View File

@ -545,7 +545,7 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
filterType := len(keyType) > 0
var keys []string
var keys []any
//keys := map[string]keyItem{}
var cursor uint64
for {
@ -559,7 +559,9 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
resp.Msg = err.Error()
return
}
keys = append(keys, loadedKey...)
for _, k := range loadedKey {
keys = append(keys, strutil.EncodeRedisKey(k))
}
//for _, k := range loadedKey {
// //t, _ := rdb.Type(ctx, k).Result()
// keys[k] = keyItem{Type: "t"}
@ -579,13 +581,14 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
}
// GetKeyValue get value by key
func (c *connectionService) GetKeyValue(connName string, db int, key, viewAs string) (resp types.JSResp) {
func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
var keyType string
var dur time.Duration
keyType, err = rdb.Type(ctx, key).Result()
@ -717,13 +720,14 @@ func (c *connectionService) GetKeyValue(connName string, db int, key, viewAs str
// SetKeyValue set value by key
// @param ttl <= 0 means keep current ttl
func (c *connectionService) SetKeyValue(connName string, db int, key, keyType string, value any, ttl int64, viewAs string) (resp types.JSResp) {
func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType string, value any, ttl int64, viewAs string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
var expiration time.Duration
if ttl < 0 {
if expiration, err = rdb.PTTL(ctx, key).Result(); err != nil {
@ -839,13 +843,14 @@ func (c *connectionService) SetKeyValue(connName string, db int, key, keyType st
}
// SetHashValue set hash field
func (c *connectionService) SetHashValue(connName string, db int, key, field, newField, value string) (resp types.JSResp) {
func (c *connectionService) SetHashValue(connName string, db int, k any, field, newField, value string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
var removedField []string
updatedField := map[string]string{}
if len(field) <= 0 {
@ -884,13 +889,14 @@ func (c *connectionService) SetHashValue(connName string, db int, key, field, ne
}
// AddHashField add or update hash field
func (c *connectionService) AddHashField(connName string, db int, key string, action int, fieldItems []any) (resp types.JSResp) {
func (c *connectionService) AddHashField(connName string, db int, k any, action int, fieldItems []any) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
updated := map[string]any{}
switch action {
case 1:
@ -929,13 +935,14 @@ func (c *connectionService) AddHashField(connName string, db int, key string, ac
}
// AddListItem add item to list or remove from it
func (c *connectionService) AddListItem(connName string, db int, key string, action int, items []any) (resp types.JSResp) {
func (c *connectionService) AddListItem(connName string, db int, k any, action int, items []any) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
var leftPush, rightPush []any
switch action {
case 0:
@ -961,13 +968,14 @@ func (c *connectionService) AddListItem(connName string, db int, key string, act
}
// SetListItem update or remove list item by index
func (c *connectionService) SetListItem(connName string, db int, key string, index int64, value string) (resp types.JSResp) {
func (c *connectionService) SetListItem(connName string, db int, k any, index int64, value string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
var removed []int64
updated := map[int64]string{}
if len(value) <= 0 {
@ -1003,13 +1011,14 @@ func (c *connectionService) SetListItem(connName string, db int, key string, ind
}
// SetSetItem add members to set or remove from set
func (c *connectionService) SetSetItem(connName string, db int, key string, remove bool, members []any) (resp types.JSResp) {
func (c *connectionService) SetSetItem(connName string, db int, k any, remove bool, members []any) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
if remove {
_, err = rdb.SRem(ctx, key, members...).Result()
} else {
@ -1025,13 +1034,14 @@ func (c *connectionService) SetSetItem(connName string, db int, key string, remo
}
// UpdateSetItem replace member of set
func (c *connectionService) UpdateSetItem(connName string, db int, key, value, newValue string) (resp types.JSResp) {
func (c *connectionService) UpdateSetItem(connName string, db int, k any, value, newValue string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
_, _ = rdb.SRem(ctx, key, value).Result()
_, err = rdb.SAdd(ctx, key, newValue).Result()
if err != nil {
@ -1044,13 +1054,14 @@ func (c *connectionService) UpdateSetItem(connName string, db int, key, value, n
}
// UpdateZSetValue update value of sorted set member
func (c *connectionService) UpdateZSetValue(connName string, db int, key, value, newValue string, score float64) (resp types.JSResp) {
func (c *connectionService) UpdateZSetValue(connName string, db int, k any, value, newValue string, score float64) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
updated := map[string]any{}
var removed []string
if len(newValue) <= 0 {
@ -1094,13 +1105,14 @@ func (c *connectionService) UpdateZSetValue(connName string, db int, key, value,
}
// AddZSetValue add item to sorted set
func (c *connectionService) AddZSetValue(connName string, db int, key string, action int, valueScore map[string]float64) (resp types.JSResp) {
func (c *connectionService) AddZSetValue(connName string, db int, k any, action int, valueScore map[string]float64) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
members := maputil.ToSlice(valueScore, func(k string) redis.Z {
return redis.Z{
Score: valueScore[k],
@ -1126,13 +1138,14 @@ func (c *connectionService) AddZSetValue(connName string, db int, key string, ac
}
// AddStreamValue add stream field
func (c *connectionService) AddStreamValue(connName string, db int, key, ID string, fieldItems []any) (resp types.JSResp) {
func (c *connectionService) AddStreamValue(connName string, db int, k any, ID string, fieldItems []any) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
_, err = rdb.XAdd(ctx, &redis.XAddArgs{
Stream: key,
ID: ID,
@ -1148,26 +1161,28 @@ func (c *connectionService) AddStreamValue(connName string, db int, key, ID stri
}
// RemoveStreamValues remove stream values by id
func (c *connectionService) RemoveStreamValues(connName string, db int, key string, IDs []string) (resp types.JSResp) {
func (c *connectionService) RemoveStreamValues(connName string, db int, k any, IDs []string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
_, err = rdb.XDel(ctx, key, IDs...).Result()
resp.Success = true
return
}
// SetKeyTTL set ttl of key
func (c *connectionService) SetKeyTTL(connName string, db int, key string, ttl int64) (resp types.JSResp) {
func (c *connectionService) SetKeyTTL(connName string, db int, k any, ttl int64) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
key := strutil.DecodeRedisKey(k)
var expiration time.Duration
if ttl < 0 {
if err = rdb.Persist(ctx, key).Err(); err != nil {
@ -1187,14 +1202,15 @@ func (c *connectionService) SetKeyTTL(connName string, db int, key string, ttl i
}
// DeleteKey remove redis key
func (c *connectionService) DeleteKey(connName string, db int, key string) (resp types.JSResp) {
func (c *connectionService) DeleteKey(connName string, db int, k any) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
var deletedKeys []string
key := strutil.DecodeRedisKey(k)
var deletedKeys []string
if strings.HasSuffix(key, "*") {
// delete by prefix
var cursor uint64

View File

@ -0,0 +1,19 @@
package strutil
import "unicode/utf8"
func containsBinary(str string) bool {
//buf := []byte(str)
//size := 0
//for start := 0; start < len(buf); start += size {
// var r rune
// if r, size = utf8.DecodeRune(buf[start:]); r == utf8.RuneError {
// return true
// }
//}
if !utf8.ValidString(str) {
return true
}
return false
}

View File

@ -14,7 +14,6 @@ import (
"strconv"
"strings"
"tinyrdm/backend/types"
"unicode/utf8"
)
// ConvertTo convert string to specified type
@ -55,7 +54,7 @@ func ConvertTo(str, targetType string) (value, resultType string) {
return
case types.HEX:
if hexStr, ok := decodeHex(str); ok {
if hexStr, ok := decodeToHex(str); ok {
value = hexStr
} else {
value = str
@ -162,8 +161,8 @@ func autoToType(str string) (value, resultType string) {
return
}
if isBinary(str) {
if value, ok = decodeHex(str); ok {
if containsBinary(str) {
if value, ok = decodeToHex(str); ok {
resultType = types.HEX
return
}
@ -175,22 +174,6 @@ func autoToType(str string) (value, resultType string) {
return
}
func isBinary(str string) bool {
//buf := []byte(str)
//size := 0
//for start := 0; start < len(buf); start += size {
// var r rune
// if r, size = utf8.DecodeRune(buf[start:]); r == utf8.RuneError {
// return true
// }
//}
if !utf8.ValidString(str) {
return true
}
return false
}
func decodeJson(str string) (string, bool) {
var data any
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
@ -220,7 +203,7 @@ func decodeBinary(str string) (string, bool) {
return binary.String(), true
}
func decodeHex(str string) (string, bool) {
func decodeToHex(str string) (string, bool) {
decodeStr := hex.EncodeToString([]byte(str))
var resultStr strings.Builder
for i := 0; i < len(decodeStr); i += 2 {

View File

@ -0,0 +1,76 @@
package strutil
import (
"strconv"
sliceutil "tinyrdm/backend/utils/slice"
)
// EncodeRedisKey encode the redis key to integer array
// if key contains binary which could not display on ui, convert the key to char array
func EncodeRedisKey(key string) any {
if containsBinary(key) {
b := []byte(key)
arr := make([]int, len(b))
for i, bb := range b {
arr[i] = int(bb)
}
return arr
}
return key
}
// DecodeRedisKey decode redis key to readable string
func DecodeRedisKey(key any) string {
switch key.(type) {
case string:
return key.(string)
case []any:
arr := key.([]any)
bytes := sliceutil.Map(arr, func(i int) byte {
if c, ok := AnyToInt(arr[i]); ok {
return byte(c)
}
return '0'
})
return string(bytes)
case []int:
arr := key.([]int)
b := make([]byte, len(arr))
for i, bb := range arr {
b[i] = byte(bb)
}
return string(b)
}
return ""
}
// AnyToInt convert any value to int
func AnyToInt(val any) (int, bool) {
switch val.(type) {
case string:
num, err := strconv.Atoi(val.(string))
if err != nil {
return 0, false
}
return num, true
case float64:
return int(val.(float64)), true
case float32:
return int(val.(float32)), true
case int64:
return int(val.(int64)), true
case int32:
return int(val.(int32)), true
case int:
return val.(int), true
case bool:
if val.(bool) {
return 1, true
} else {
return 0, true
}
}
return 0, false
}

View File

@ -1,6 +1,7 @@
<script setup>
import { computed } from 'vue'
import { typesBgColor, typesColor, validType } from '@/consts/support_redis_type.js'
import Binary from '@/components/icons/Binary.vue'
const props = defineProps({
type: {
@ -10,6 +11,7 @@ const props = defineProps({
},
default: 'STRING',
},
binaryKey: Boolean,
bordered: Boolean,
size: String,
})
@ -31,6 +33,9 @@ const backgroundColor = computed(() => {
:size="props.size"
strong>
{{ props.type }}
<template #icon>
<n-icon v-if="binaryKey" :component="Binary" size="18" />
</template>
</n-tag>
<!-- <div class="redis-type-tag flex-box-h" :style="{backgroundColor: backgroundColor}">{{ props.type }}</div>-->
</template>

View File

@ -165,6 +165,7 @@ const tabContent = computed(() => {
type: toUpper(tab.type),
db: tab.db,
keyPath: tab.key,
keyCode: tab.keyCode,
ttl: tab.ttl,
value: tab.value,
size: tab.size || 0,
@ -217,6 +218,7 @@ const onReloadKey = async () => {
v-else
:db="tabContent.db"
:key-path="tabContent.keyPath"
:key-code="tabContent.keyCode"
:name="tabContent.name"
:ttl="tabContent.ttl"
:value="tabContent.value"

View File

@ -11,6 +11,8 @@ import IconButton from '@/components/common/IconButton.vue'
import useConnectionStore from 'stores/connections.js'
import Copy from '@/components/icons/Copy.vue'
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
import { computed } from 'vue'
import { isEmpty } from 'lodash'
const props = defineProps({
server: String,
@ -23,6 +25,10 @@ const props = defineProps({
default: 'STRING',
},
keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: {
type: Number,
default: -1,
@ -33,8 +39,20 @@ const dialogStore = useDialog()
const connectionStore = useConnectionStore()
const i18n = useI18n()
const binaryKey = computed(() => {
return !!props.keyCode
})
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const onReloadKey = () => {
connectionStore.loadKeyValue(props.server, props.db, props.keyPath)
connectionStore.loadKeyValue(props.server, props.db, keyName.value)
}
const onCopyKey = () => {
@ -49,9 +67,17 @@ const onCopyKey = () => {
})
}
const onRenameKey = () => {
if (binaryKey.value) {
$message.error(i18n.t('dialogue.rename_binary_key_fail'))
} else {
dialogStore.openRenameKeyDialog(props.server, props.db, props.keyPath)
}
}
const onDeleteKey = () => {
$dialog.warning(i18n.t('dialogue.remove_tip', { name: props.keyPath }), () => {
connectionStore.deleteKey(props.server, props.db, props.keyPath).then((success) => {
connectionStore.deleteKey(props.server, props.db, keyName.value).then((success) => {
if (success) {
$message.success(i18n.t('dialogue.delete_key_succ', { key: props.keyPath }))
}
@ -63,7 +89,7 @@ const onDeleteKey = () => {
<template>
<div class="content-toolbar flex-box-h">
<n-input-group>
<redis-type-tag :type="props.keyType" size="large" />
<redis-type-tag :type="props.keyType" :binary-key="binaryKey" size="large" />
<n-input v-model:value="props.keyPath">
<template #suffix>
<icon-button :icon="Refresh" size="18" t-tooltip="interface.reload" @click="onReloadKey" />
@ -86,18 +112,13 @@ const onDeleteKey = () => {
</template>
TTL
</n-tooltip>
<icon-button
:icon="Edit"
border
size="18"
t-tooltip="interface.rename_key"
@click="dialogStore.openRenameKeyDialog(props.server, props.db, props.keyPath)" />
<icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="onRenameKey" />
</n-button-group>
<n-tooltip>
<template #trigger>
<n-button>
<n-button :focusable="false" @click="onDeleteKey">
<template #icon>
<n-icon :component="Delete" size="18" @click="onDeleteKey" />
<n-icon :component="Delete" size="18" />
</template>
</n-button>
</template>

View File

@ -8,12 +8,17 @@ import { types, types as redisTypes } from '@/consts/support_redis_type.js'
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
import useDialogStore from 'stores/dialog.js'
import useConnectionStore from 'stores/connections.js'
import { isEmpty } from 'lodash'
const i18n = useI18n()
const props = defineProps({
name: String,
db: Number,
keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: {
type: Number,
default: -1,
@ -22,6 +27,14 @@ const props = defineProps({
size: Number,
})
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const filterOption = [
{
value: 1,
@ -119,11 +132,11 @@ const actionColumn = {
const { success, msg } = await connectionStore.removeHashField(
props.name,
props.db,
props.keyPath,
keyName.value,
row.key,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.delete_key_succ', { key: row.key }))
// update display value
// if (!isEmpty(removed)) {
@ -143,13 +156,13 @@ const actionColumn = {
const { success, msg } = await connectionStore.setHash(
props.name,
props.db,
props.keyPath,
keyName.value,
row.key,
currentEditRow.value.key,
currentEditRow.value.value,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.save_value_succ'))
// update display value
// if (!isEmpty(updated)) {
@ -198,7 +211,7 @@ const tableData = computed(() => {
return data
})
const onAddRow = () => {
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, types.HASH)
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.HASH)
}
const filterValue = ref('')
@ -240,7 +253,13 @@ const onUpdateFilter = (filters, sourceColumn) => {
<template>
<div class="content-wrapper flex-box-v">
<content-toolbar :db="props.db" :key-path="props.keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
<content-toolbar
:db="props.db"
:key-path="props.keyPath"
:key-code="props.keyCode"
:key-type="keyType"
:server="props.name"
:ttl="ttl" />
<div class="tb2 flex-box-h">
<div class="flex-box-h">
<n-input-group>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NCode, NIcon, NInput } from 'naive-ui'
import { size } from 'lodash'
import { isEmpty, size } from 'lodash'
import { types, types as redisTypes } from '@/consts/support_redis_type.js'
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
import useDialogStore from 'stores/dialog.js'
@ -15,6 +15,10 @@ const props = defineProps({
name: String,
db: Number,
keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: {
type: Number,
default: -1,
@ -23,6 +27,14 @@ const props = defineProps({
size: Number,
})
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const connectionStore = useConnectionStore()
const dialogStore = useDialogStore()
const keyType = redisTypes.LIST
@ -56,6 +68,7 @@ const valueColumn = reactive({
}
},
})
const actionColumn = {
key: 'action',
title: i18n.t('interface.action'),
@ -76,11 +89,11 @@ const actionColumn = {
const { success, msg } = await connectionStore.removeListItem(
props.name,
props.db,
props.keyPath,
keyName.value,
row.no - 1,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.delete_key_succ', { key: '#' + row.no }))
// update display value
// if (!isEmpty(removed)) {
@ -98,12 +111,12 @@ const actionColumn = {
const { success, msg } = await connectionStore.updateListItem(
props.name,
props.db,
props.keyPath,
keyName.value,
currentEditRow.value.no - 1,
currentEditRow.value.value,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.save_value_succ'))
// update display value
// if (!isEmpty(updated)) {
@ -153,7 +166,7 @@ const tableData = computed(() => {
})
const onAddValue = (value) => {
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, types.LIST)
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.LIST)
}
const filterValue = ref('')
@ -172,7 +185,13 @@ const onUpdateFilter = (filters, sourceColumn) => {
<template>
<div class="content-wrapper flex-box-v">
<content-toolbar :db="props.db" :key-path="props.keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
<content-toolbar
:db="props.db"
:key-path="props.keyPath"
:key-code="props.keyCode"
:key-type="keyType"
:server="props.name"
:ttl="ttl" />
<div class="tb2 flex-box-h">
<div class="flex-box-h">
<n-input

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NCode, NIcon, NInput } from 'naive-ui'
import { size } from 'lodash'
import { isEmpty, size } from 'lodash'
import useDialogStore from 'stores/dialog.js'
import { types, types as redisTypes } from '@/consts/support_redis_type.js'
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
@ -15,6 +15,10 @@ const props = defineProps({
name: String,
db: Number,
keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: {
type: Number,
default: -1,
@ -23,6 +27,14 @@ const props = defineProps({
size: Number,
})
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const connectionStore = useConnectionStore()
const dialogStore = useDialogStore()
const keyType = redisTypes.SET
@ -78,11 +90,11 @@ const actionColumn = {
const { success, msg } = await connectionStore.removeSetItem(
props.name,
props.db,
props.keyPath,
keyName.value,
row.value,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.delete_key_succ', { key: row.value }))
// update display value
// props.value.splice(row.no - 1, 1)
@ -98,12 +110,12 @@ const actionColumn = {
const { success, msg } = await connectionStore.updateSetItem(
props.name,
props.db,
props.keyPath,
keyName.value,
row.value,
currentEditRow.value.value,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.save_value_succ'))
// update display value
// props.value[row.no - 1] = currentEditRow.value.value
@ -149,7 +161,7 @@ const tableData = computed(() => {
})
const onAddValue = (value) => {
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, types.SET)
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.SET)
}
const filterValue = ref('')
@ -168,7 +180,13 @@ const onUpdateFilter = (filters, sourceColumn) => {
<template>
<div class="content-wrapper flex-box-v">
<content-toolbar :db="props.db" :key-path="props.keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
<content-toolbar
:db="props.db"
:key-path="props.keyPath"
:key-code="props.keyCode"
:key-type="keyType"
:server="props.name"
:ttl="ttl" />
<div class="tb2 flex-box-h">
<div class="flex-box-h">
<n-input

View File

@ -8,13 +8,17 @@ import { types, types as redisTypes } from '@/consts/support_redis_type.js'
import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
import useDialogStore from 'stores/dialog.js'
import useConnectionStore from 'stores/connections.js'
import { includes, keys, some, values } from 'lodash'
import { includes, isEmpty, keys, some, values } from 'lodash'
const i18n = useI18n()
const props = defineProps({
name: String,
db: Number,
keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: {
type: Number,
default: -1,
@ -23,6 +27,14 @@ const props = defineProps({
size: Number,
})
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const filterOption = [
{
value: 1,
@ -86,11 +98,11 @@ const actionColumn = {
const { success, msg } = await connectionStore.removeStreamValues(
props.name,
props.db,
props.keyPath,
keyName.value,
row.id,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.delete_key_succ', { key: row.id }))
// update display value
// if (!isEmpty(removed)) {
@ -122,7 +134,7 @@ const tableData = computed(() => {
})
const onAddRow = () => {
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, types.STREAM)
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.STREAM)
}
const filterValue = ref('')
@ -153,7 +165,13 @@ const onUpdateFilter = (filters, sourceColumn) => {
<template>
<div class="content-wrapper flex-box-v">
<content-toolbar :db="props.db" :key-path="props.keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
<content-toolbar
:db="props.db"
:key-path="props.keyPath"
:key-code="props.keyCode"
:key-type="keyType"
:server="props.name"
:ttl="ttl" />
<div class="tb2 flex-box-h">
<div class="flex-box-h">
<n-input-group>

View File

@ -10,7 +10,7 @@ import Close from '@/components/icons/Close.vue'
import Edit from '@/components/icons/Edit.vue'
import { types as redisTypes } from '@/consts/support_redis_type.js'
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
import { map, toLower } from 'lodash'
import { isEmpty, map, toLower } from 'lodash'
import useConnectionStore from 'stores/connections.js'
const i18n = useI18n()
@ -20,6 +20,10 @@ const props = defineProps({
name: String,
db: Number,
keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: {
type: Number,
default: -1,
@ -32,6 +36,14 @@ const props = defineProps({
},
})
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const viewOption = computed(() =>
map(types, (t) => {
return {
@ -76,7 +88,7 @@ const viewLanguage = computed(() => {
})
const onViewTypeUpdate = (viewType) => {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath, viewType)
connectionStore.loadKeyValue(props.name, props.db, keyName.value, viewType)
}
/**
@ -116,14 +128,14 @@ const onSaveValue = async () => {
const { success, msg } = await connectionStore.setKey(
props.name,
props.db,
props.keyPath,
keyName.value,
toLower(keyType),
editValue.value,
-1,
props.viewAs,
)
if (success) {
await connectionStore.loadKeyValue(props.name, props.db, props.keyPath)
await connectionStore.loadKeyValue(props.name, props.db, keyName.value)
$message.success(i18n.t('dialogue.save_value_succ'))
} else {
$message.error(msg)
@ -139,13 +151,19 @@ const onSaveValue = async () => {
<template>
<div class="content-wrapper flex-box-v">
<content-toolbar :db="props.db" :key-path="keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
<content-toolbar
:db="props.db"
:key-path="keyPath"
:key-code="keyCode"
:key-type="keyType"
:server="props.name"
:ttl="ttl" />
<div class="tb2 flex-box-h">
<n-text>{{ $t('interface.view_as') }}</n-text>
<n-select
:value="props.viewAs"
:options="viewOption"
style="width: 200px"
style="width: 160px"
filterable
@update:value="onViewTypeUpdate" />
<div class="flex-item-expand"></div>

View File

@ -15,6 +15,10 @@ const props = defineProps({
name: String,
db: Number,
keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: {
type: Number,
default: -1,
@ -23,6 +27,14 @@ const props = defineProps({
size: Number,
})
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const filterOption = [
{
value: 1,
@ -149,11 +161,11 @@ const actionColumn = {
const { success, msg } = await connectionStore.removeZSetItem(
props.name,
props.db,
props.keyPath,
keyName.value,
row.value,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.delete_key_succ', { key: row.value }))
} else {
$message.error(msg)
@ -172,13 +184,13 @@ const actionColumn = {
const { success, msg } = await connectionStore.updateZSetItem(
props.name,
props.db,
props.keyPath,
keyName.value,
row.value,
newValue,
currentEditRow.value.score,
)
if (success) {
connectionStore.loadKeyValue(props.name, props.db, props.keyPath).then((r) => {})
connectionStore.loadKeyValue(props.name, props.db, keyName.value).then((r) => {})
$message.success(i18n.t('dialogue.save_value_succ'))
} else {
$message.error(msg)
@ -222,7 +234,7 @@ const tableData = computed(() => {
})
const onAddRow = () => {
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, types.ZSET)
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.ZSET)
}
const filterValue = ref('')
@ -266,7 +278,13 @@ const onUpdateFilter = (filters, sourceColumn) => {
<template>
<div class="content-wrapper flex-box-v">
<content-toolbar :db="props.db" :key-path="props.keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
<content-toolbar
:db="props.db"
:key-path="props.keyPath"
:key-code="props.keyCode"
:key-type="keyType"
:server="props.name"
:ttl="ttl" />
<div class="tb2 flex-box-h">
<div class="flex-box-h">
<n-input-group>

View File

@ -10,13 +10,14 @@ import AddHashValue from '@/components/new_value/AddHashValue.vue'
import AddZSetValue from '@/components/new_value/AddZSetValue.vue'
import useConnectionStore from 'stores/connections.js'
import NewStreamValue from '@/components/new_value/NewStreamValue.vue'
import { size, slice } from 'lodash'
import { isEmpty, size, slice } from 'lodash'
const i18n = useI18n()
const newForm = reactive({
server: '',
db: 0,
key: '',
keyCode: null,
type: '',
opType: 0,
value: null,
@ -65,10 +66,11 @@ watch(
() => dialogStore.addFieldsDialogVisible,
(visible) => {
if (visible) {
const { server, db, key, type } = dialogStore.addFieldParam
const { server, db, key, keyCode, type } = dialogStore.addFieldParam
newForm.server = server
newForm.db = db
newForm.key = key
newForm.keyCode = keyCode
newForm.type = type
newForm.opType = 0
newForm.value = null
@ -79,24 +81,25 @@ watch(
const connectionStore = useConnectionStore()
const onAdd = async () => {
try {
const { server, db, key, type } = newForm
const { server, db, key, keyCode, type } = newForm
let { value } = newForm
if (value == null) {
value = defaultValue[type]
}
const keyName = isEmpty(keyCode) ? key : keyCode
switch (type) {
case types.LIST:
{
let data
if (newForm.opType === 1) {
data = await connectionStore.prependListItem(server, db, key, value)
data = await connectionStore.prependListItem(server, db, keyName, value)
} else {
data = await connectionStore.appendListItem(server, db, key, value)
data = await connectionStore.appendListItem(server, db, keyName, value)
}
const { success, msg } = data
if (success) {
if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {})
connectionStore.loadKeyValue(server, db, keyName).then(() => {})
}
$message.success(i18n.t('dialogue.handle_succ'))
} else {
@ -107,10 +110,16 @@ const onAdd = async () => {
case types.HASH:
{
const { success, msg } = await connectionStore.addHashField(server, db, key, newForm.opType, value)
const { success, msg } = await connectionStore.addHashField(
server,
db,
keyName,
newForm.opType,
value,
)
if (success) {
if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {})
connectionStore.loadKeyValue(server, db, keyName).then(() => {})
}
$message.success(i18n.t('dialogue.handle_succ'))
} else {
@ -121,10 +130,10 @@ const onAdd = async () => {
case types.SET:
{
const { success, msg } = await connectionStore.addSetItem(server, db, key, value)
const { success, msg } = await connectionStore.addSetItem(server, db, keyName, value)
if (success) {
if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {})
connectionStore.loadKeyValue(server, db, keyName).then(() => {})
}
$message.success(i18n.t('dialogue.handle_succ'))
} else {
@ -135,10 +144,16 @@ const onAdd = async () => {
case types.ZSET:
{
const { success, msg } = await connectionStore.addZSetItem(server, db, key, newForm.opType, value)
const { success, msg } = await connectionStore.addZSetItem(
server,
db,
keyName,
newForm.opType,
value,
)
if (success) {
if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {})
connectionStore.loadKeyValue(server, db, keyName).then(() => {})
}
$message.success(i18n.t('dialogue.handle_succ'))
} else {
@ -153,13 +168,13 @@ const onAdd = async () => {
const { success, msg } = await connectionStore.addStreamValue(
server,
db,
key,
keyName,
value[0],
slice(value, 1),
)
if (success) {
if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {})
connectionStore.loadKeyValue(server, db, keyName).then(() => {})
}
$message.success(i18n.t('dialogue.handle_succ'))
} else {

View File

@ -3,8 +3,14 @@ import { reactive, ref, watch } from 'vue'
import useDialog from 'stores/dialog'
import useTabStore from 'stores/tab.js'
import useConnectionStore from 'stores/connections.js'
import Binary from '@/components/icons/Binary.vue'
import { isEmpty } from 'lodash'
const ttlForm = reactive({
server: '',
db: 0,
key: '',
keyCode: null,
ttl: -1,
})
@ -12,9 +18,6 @@ const dialogStore = useDialog()
const connectionStore = useConnectionStore()
const tabStore = useTabStore()
const currentServer = ref('')
const currentKey = ref('')
const currentDB = ref(0)
watch(
() => dialogStore.ttlDialogVisible,
(visible) => {
@ -22,15 +25,15 @@ watch(
// get ttl from current tab
const tab = tabStore.currentTab
if (tab != null) {
ttlForm.server = tab.name
ttlForm.db = tab.db
ttlForm.key = tab.key
ttlForm.keyCode = tab.keyCode
if (tab.ttl < 0) {
// forever
} else {
ttlForm.ttl = tab.ttl
}
currentServer.value = tab.name
currentDB.value = tab.db
currentKey.value = tab.key
}
}
},
@ -46,16 +49,18 @@ const onConfirm = async () => {
if (tab == null) {
return
}
const success = await connectionStore.setTTL(tab.name, tab.db, tab.key, ttlForm.ttl)
const key = isEmpty(ttlForm.keyCode) ? ttlForm.key : ttlForm.keyCode
const success = await connectionStore.setTTL(tab.name, tab.db, key, ttlForm.ttl)
if (success) {
tabStore.updateTTL({
server: currentServer.value,
db: currentDB.value,
key: currentKey.value,
server: ttlForm.server,
db: ttlForm.db,
key: ttlForm.key,
ttl: ttlForm.ttl,
})
}
} catch (e) {
$message.error(e.message || 'set ttl fail')
} finally {
dialogStore.closeTTLDialog()
}
@ -74,7 +79,11 @@ const onConfirm = async () => {
transform-origin="center">
<n-form :model="ttlForm" :show-require-mark="false" label-placement="top">
<n-form-item :label="$t('common.key')">
<n-input :value="currentKey" readonly />
<n-input :value="ttlForm.key" readonly>
<template #prefix>
<n-icon v-if="!!ttlForm.keyCode" :component="Binary" size="20" />
</template>
</n-input>
</n-form-item>
<n-form-item :label="$t('interface.ttl')" required>
<n-input-number

View File

@ -0,0 +1,23 @@
<script setup></script>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round">
<rect x="14" y="14" width="4" height="6" rx="2" />
<rect x="6" y="4" width="4" height="6" rx="2" />
<path d="M6 20h4" />
<path d="M14 10h4" />
<path d="M6 14h2v6" />
<path d="M14 4h2v6" />
</svg>
</template>
<style lang="scss" scoped></style>

View File

@ -3,6 +3,7 @@ import { computed, h, nextTick, onMounted, reactive, ref } from 'vue'
import { ConnectionType } from '@/consts/connection_type.js'
import { NIcon, NSpace, NTag } from 'naive-ui'
import Key from '@/components/icons/Key.vue'
import Binary from '@/components/icons/Binary.vue'
import ToggleDb from '@/components/icons/ToggleDb.vue'
import { find, get, includes, indexOf, isEmpty, pull, remove, size } from 'lodash'
import { useI18n } from 'vue-i18n'
@ -223,7 +224,9 @@ const handleSelectContextMenu = (key) => {
return
}
const node = connectionStore.getNode(selectedKey)
const { db, key: nodeKey, redisKey } = node || {}
const { db, key: nodeKey } = node || {}
const redisKey = node.redisKeyCode || node.redisKey
const redisKeyName = !!node.redisKeyCode ? node.label : redisKey
switch (key) {
case 'server_info':
tabStore.setSelectedKeys(props.server)
@ -267,10 +270,10 @@ const handleSelectContextMenu = (key) => {
dialogStore.openDeleteKeyDialog(props.server, db, isEmpty(redisKey) ? '*' : redisKey + ':*')
break
case 'value_remove':
$dialog.warning(i18n.t('dialogue.remove_tip', { name: redisKey }), () => {
$dialog.warning(i18n.t('dialogue.remove_tip', { name: redisKeyName }), () => {
connectionStore.deleteKey(props.server, db, redisKey).then((success) => {
if (success) {
$message.success(i18n.t('dialogue.delete_key_succ', { key: redisKey }))
$message.success(i18n.t('dialogue.delete_key_succ', { key: redisKeyName }))
}
})
})
@ -327,7 +330,8 @@ const onUpdateSelectedKeys = (keys, options) => {
// prevent load duplicate key
for (const node of options) {
if (node.type === ConnectionType.RedisValue) {
const { key, db, redisKey } = node
const { key, db } = node
const redisKey = node.redisKeyCode || node.redisKey
if (!includes(selectedKeys.value, key)) {
connectionStore.loadKeyValue(props.server, db, redisKey)
}
@ -373,7 +377,7 @@ const renderPrefix = ({ option }) => {
NIcon,
{ size: 20 },
{
default: () => h(Key),
default: () => h(!!option.redisKeyCode ? Binary : Key),
},
)
}

View File

@ -80,6 +80,7 @@
"batch_delete": "Batch Delete",
"copy_path": "Copy Path",
"copy_key": "Copy Key",
"binary_key": "Binary Key Name",
"remove_key": "Remove Key",
"new_key": "Add New Key",
"nonexist_tab_content": "Selected key does not exist. Please retry",
@ -105,6 +106,7 @@
"delete_key_succ": "\"{key}\" has been deleted",
"save_value_succ": "Value Saved !",
"copy_succ": "Value Copied !",
"rename_binary_key_fail": "Rename binary key name is unsupported",
"handle_succ": "Success!",
"reload_succ": "Reloaded!",
"field_required": "This item should not be blank",

View File

@ -80,6 +80,7 @@
"batch_delete": "批量删除键",
"copy_path": "复制路径",
"copy_key": "复制键名",
"binary_key": "二进制键名",
"remove_key": "删除键",
"new_key": "添加新键",
"nonexist_tab_content": "所选键不存在,请尝试刷新重试",
@ -105,6 +106,7 @@
"delete_key_succ": "{key} 已被删除",
"save_value_succ": "已保存值",
"copy_succ": "已复制到剪切板",
"rename_binary_key_fail": "不支持重命名二进制键名",
"handle_succ": "操作成功",
"reload_succ": "已重新载入",
"field_required": "此项不能为空",

View File

@ -5,6 +5,7 @@ import {
get,
isEmpty,
join,
map,
remove,
size,
slice,
@ -49,6 +50,7 @@ import {
import { ConnectionType } from '@/consts/connection_type.js'
import useTabStore from './tab.js'
import { types } from '@/consts/support_redis_type.js'
import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js'
const useConnectionStore = defineStore('connections', {
/**
@ -68,6 +70,7 @@ const useConnectionStore = defineStore('connections', {
* @property {number} type
* @property {number} [db] - database index, type == ConnectionType.RedisDB only
* @property {string} [redisKey] - redis key, type == ConnectionType.RedisKey || type == ConnectionType.RedisValue only
* @property {string} [redisKeyCode] - redis key char code array, optional for redis key which contains binary data
* @property {number} [keys] - children key count
* @property {boolean} [isLeaf]
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
@ -592,7 +595,7 @@ const useConnectionStore = defineStore('connections', {
* load redis key
* @param {string} server
* @param {number} db
* @param {string} [key] when key is null or blank, update tab to display normal content (blank content or server status)
* @param {string|number[]} [key] when key is null or blank, update tab to display normal content (blank content or server status)
* @param {string} [viewType]
*/
async loadKeyValue(server, db, key, viewType) {
@ -602,12 +605,15 @@ const useConnectionStore = defineStore('connections', {
const { data, success, msg } = await GetKeyValue(server, db, key, viewType)
if (success) {
const { type, ttl, value, size, viewAs } = data
const k = decodeRedisKey(key)
const binaryKey = k !== key
tab.upsertTab({
server,
db,
type,
ttl,
key,
keyCode: binaryKey ? key : undefined,
key: k,
value,
size,
viewAs,
@ -629,6 +635,7 @@ const useConnectionStore = defineStore('connections', {
type: 'none',
ttl: -1,
key: null,
keyCode: null,
value: null,
size: 0,
})
@ -713,7 +720,7 @@ const useConnectionStore = defineStore('connections', {
* remove keys in db
* @param {string} connName
* @param {number} db
* @param {string[]} keys
* @param {Array<string|number[]>} keys
* @param {boolean} [sortInsert]
* @return {{success: boolean, newKey: number, newLayer: number, replaceKey: number}}
* @private
@ -735,7 +742,9 @@ const useConnectionStore = defineStore('connections', {
const nodeMap = this._getNodeMap(connName, db)
const rootChildren = selDB.children
for (const key of keys) {
const keyParts = split(key, separator)
const k = decodeRedisKey(key)
const binaryKey = k !== key
const keyParts = binaryKey ? [nativeRedisKey(key)] : split(k, separator)
const len = size(keyParts)
const lastIdx = len - 1
let handlePath = ''
@ -776,10 +785,11 @@ const useConnectionStore = defineStore('connections', {
const replaceKey = nodeMap.has(nodeKey)
const selectedNode = {
key: `${connName}/db${db}#${nodeKey}`,
label: keyParts[i],
label: binaryKey ? k : keyParts[i],
db,
keys: 0,
redisKey: handlePath,
redisKeyCode: binaryKey ? key : undefined,
type: ConnectionType.RedisValue,
isLeaf: true,
}
@ -926,7 +936,7 @@ const useConnectionStore = defineStore('connections', {
* set redis key
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {string} keyType
* @param {any} value
* @param {number} ttl
@ -959,7 +969,7 @@ const useConnectionStore = defineStore('connections', {
* when both field and newField are set, and field !== newField, delete field and add newField
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {string} field
* @param {string} newField
* @param {string} value
@ -983,7 +993,7 @@ const useConnectionStore = defineStore('connections', {
* insert or update hash field item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields
* @param {string[]} fieldItems field1, value1, filed2, value2...
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
@ -1028,7 +1038,7 @@ const useConnectionStore = defineStore('connections', {
* insert list item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {int} action 0: push to head, 1: push to tail
* @param {string[]}values
* @returns {Promise<*|{msg, success: boolean}>}
@ -1089,7 +1099,7 @@ const useConnectionStore = defineStore('connections', {
* update value of list item by index
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {number} index
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
@ -1112,7 +1122,7 @@ const useConnectionStore = defineStore('connections', {
* remove list item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {number} index
* @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>}
*/
@ -1134,7 +1144,7 @@ const useConnectionStore = defineStore('connections', {
* add item to set
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number} key
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
@ -1155,7 +1165,7 @@ const useConnectionStore = defineStore('connections', {
* update value of set item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {string} value
* @param {string} newValue
* @returns {Promise<{[msg]: string, success: boolean}>}
@ -1175,10 +1185,10 @@ const useConnectionStore = defineStore('connections', {
/**
* remove item from set
* @param connName
* @param db
* @param key
* @param value
* @param {string} connName
* @param {number} db
* @param {string|number[]} key
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
async removeSetItem(connName, db, key, value) {
@ -1198,7 +1208,7 @@ const useConnectionStore = defineStore('connections', {
* add item to sorted set
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {number} action
* @param {Object.<string, number>} vs value: score
* @returns {Promise<{[msg]: string, success: boolean}>}
@ -1220,7 +1230,7 @@ const useConnectionStore = defineStore('connections', {
* update item of sorted set
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {string} value
* @param {string} newValue
* @param {number} score
@ -1244,7 +1254,7 @@ const useConnectionStore = defineStore('connections', {
* remove item from sorted set
* @param {string} connName
* @param {number} db
* @param key
* @param {string|number[]} key
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean, [removed]: []}>}
*/
@ -1266,7 +1276,7 @@ const useConnectionStore = defineStore('connections', {
* insert new stream field item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {string} id
* @param {string[]} values field1, value1, filed2, value2...
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
@ -1289,7 +1299,7 @@ const useConnectionStore = defineStore('connections', {
* remove stream field
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {string[]|string} ids
* @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>}
*/
@ -1427,7 +1437,7 @@ const useConnectionStore = defineStore('connections', {
* delete redis key
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {boolean} [soft] do not try to remove from redis if true, just remove from tree data
* @returns {Promise<boolean>}
*/
@ -1437,9 +1447,10 @@ const useConnectionStore = defineStore('connections', {
await DeleteKey(connName, db, key)
}
const k = nativeRedisKey(key)
// update tree view data
this._deleteKeyNode(connName, db, key)
this._tidyNode(connName, db, key, true)
this._deleteKeyNode(connName, db, k)
this._tidyNode(connName, db, k, true)
// set tab content empty
const tab = useTabStore()

View File

@ -45,6 +45,7 @@ const useDialogStore = defineStore('dialog', {
server: '',
db: 0,
key: '',
keyCode: null,
type: null,
},
addFieldsDialogVisible: false,
@ -185,12 +186,14 @@ const useDialogStore = defineStore('dialog', {
* @param {string} server
* @param {number} db
* @param {string} key
* @param {number[]|null} keyCode
* @param {string} type
*/
openAddFieldsDialog(server, db, key, type) {
openAddFieldsDialog(server, db, key, keyCode, type) {
this.addFieldParam.server = server
this.addFieldParam.db = db
this.addFieldParam.key = key
this.addFieldParam.keyCode = keyCode
this.addFieldParam.type = type
this.addFieldsDialogVisible = true
},

View File

@ -14,6 +14,7 @@ const useTabStore = defineStore('tab', {
* @property {string} [server] server name
* @property {int} [db] database index
* @property {string} [key] current key name
* @property {number[]|null|undefined} [keyCode] current key name as char array
* @property {int} [ttl] ttl of current key
*/
@ -83,11 +84,12 @@ const useTabStore = defineStore('tab', {
* @param {number} [type]
* @param {number} [ttl]
* @param {string} [key]
* @param {string} [keyCode]
* @param {number} [size]
* @param {*} [value]
* @param {string} [viewAs]
*/
upsertTab({ server, db, type, ttl, key, size, value, viewAs }) {
upsertTab({ server, db, type, ttl, key, keyCode, size, value, viewAs }) {
let tabIndex = findIndex(this.tabList, { name: server })
if (tabIndex === -1) {
this.tabList.push({
@ -97,6 +99,7 @@ const useTabStore = defineStore('tab', {
type,
ttl,
key,
keyCode,
size,
value,
viewAs,
@ -112,6 +115,7 @@ const useTabStore = defineStore('tab', {
tab.type = type
tab.ttl = ttl
tab.key = key
tab.keyCode = keyCode
tab.size = size
tab.value = value
tab.viewAs = viewAs
@ -123,7 +127,7 @@ const useTabStore = defineStore('tab', {
* update ttl by tag
* @param {string} server
* @param {number} db
* @param {string} key
* @param {string|number[]} key
* @param {number} ttl
*/
updateTTL({ server, db, key, ttl }) {

View File

@ -0,0 +1,35 @@
import { join, map } from 'lodash'
/**
* converted binary data in strings to hex format
* @param {string|number[]} key
* @return {string}
*/
export function decodeRedisKey(key) {
if (key instanceof Array) {
// char array, convert to hex string
return join(
map(key, (k) => {
if (k >= 32 && k <= 126) {
return String.fromCharCode(k)
}
return '\\x' + k.toString(16).toUpperCase().padStart(2, '0')
}),
'',
)
}
return key
}
/**
* convert char code array to string
* @param {string|number[]} key
* @return {string}
*/
export function nativeRedisKey(key) {
if (key instanceof Array) {
return map(key, (c) => String.fromCharCode(c)).join('')
}
return key
}

View File

@ -13,7 +13,7 @@
"info": {
"companyName": "Tiny Craft",
"productName": "Tiny RDM",
"productVersion": "0.9.0",
"productVersion": "1.0.0",
"copyright": "Copyright © 2023",
"comments": "Tiny Redis Desktop Manager"
}