Compare commits

...

5 Commits

24 changed files with 514 additions and 151 deletions

View File

@ -327,7 +327,7 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
// get connection config // get connection config
selConn := c.conns.GetConnection(name) selConn := c.conns.GetConnection(name)
totaldb := 16 var totaldb int
if selConn.DBFilterType == "" || selConn.DBFilterType == "none" { if selConn.DBFilterType == "" || selConn.DBFilterType == "none" {
// get total databases // get total databases
if config, err := rdb.ConfigGet(ctx, "databases").Result(); err == nil { if config, err := rdb.ConfigGet(ctx, "databases").Result(); err == nil {
@ -343,9 +343,23 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
resp.Msg = "get server info fail:" + err.Error() resp.Msg = "get server info fail:" + err.Error()
return return
} }
// Parse all db, response content like below // parse all db, response content like below
var dbs []types.ConnectionDB var dbs []types.ConnectionDB
info := c.parseInfo(res) info := c.parseInfo(res)
if totaldb <= 0 {
// cannot retrieve the database count by "CONFIG GET databases", try to get max index from keyspace
keyspace := info["Keyspace"]
var db, maxDB int
for dbName := range keyspace {
if db, err = strconv.Atoi(strings.TrimLeft(dbName, "db")); err == nil {
if maxDB < db {
maxDB = db
}
}
}
totaldb = maxDB + 1
}
queryDB := func(idx int) types.ConnectionDB { queryDB := func(idx int) types.ConnectionDB {
dbName := "db" + strconv.Itoa(idx) dbName := "db" + strconv.Itoa(idx)
dbInfoStr := info["Keyspace"][dbName] dbInfoStr := info["Keyspace"][dbName]
@ -531,7 +545,7 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
filterType := len(keyType) > 0 filterType := len(keyType) > 0
var keys []string var keys []any
//keys := map[string]keyItem{} //keys := map[string]keyItem{}
var cursor uint64 var cursor uint64
for { for {
@ -545,7 +559,9 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
keys = append(keys, loadedKey...) for _, k := range loadedKey {
keys = append(keys, strutil.EncodeRedisKey(k))
}
//for _, k := range loadedKey { //for _, k := range loadedKey {
// //t, _ := rdb.Type(ctx, k).Result() // //t, _ := rdb.Type(ctx, k).Result()
// keys[k] = keyItem{Type: "t"} // keys[k] = keyItem{Type: "t"}
@ -565,13 +581,14 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
} }
// GetKeyValue get value by key // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
var keyType string var keyType string
var dur time.Duration var dur time.Duration
keyType, err = rdb.Type(ctx, key).Result() keyType, err = rdb.Type(ctx, key).Result()
@ -703,13 +720,14 @@ func (c *connectionService) GetKeyValue(connName string, db int, key, viewAs str
// SetKeyValue set value by key // SetKeyValue set value by key
// @param ttl <= 0 means keep current ttl // @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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
var expiration time.Duration var expiration time.Duration
if ttl < 0 { if ttl < 0 {
if expiration, err = rdb.PTTL(ctx, key).Result(); err != nil { if expiration, err = rdb.PTTL(ctx, key).Result(); err != nil {
@ -825,13 +843,14 @@ func (c *connectionService) SetKeyValue(connName string, db int, key, keyType st
} }
// SetHashValue set hash field // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
var removedField []string var removedField []string
updatedField := map[string]string{} updatedField := map[string]string{}
if len(field) <= 0 { if len(field) <= 0 {
@ -870,13 +889,14 @@ func (c *connectionService) SetHashValue(connName string, db int, key, field, ne
} }
// AddHashField add or update hash field // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
updated := map[string]any{} updated := map[string]any{}
switch action { switch action {
case 1: case 1:
@ -915,13 +935,14 @@ func (c *connectionService) AddHashField(connName string, db int, key string, ac
} }
// AddListItem add item to list or remove from it // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
var leftPush, rightPush []any var leftPush, rightPush []any
switch action { switch action {
case 0: case 0:
@ -947,13 +968,14 @@ func (c *connectionService) AddListItem(connName string, db int, key string, act
} }
// SetListItem update or remove list item by index // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
var removed []int64 var removed []int64
updated := map[int64]string{} updated := map[int64]string{}
if len(value) <= 0 { if len(value) <= 0 {
@ -989,13 +1011,14 @@ func (c *connectionService) SetListItem(connName string, db int, key string, ind
} }
// SetSetItem add members to set or remove from set // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
if remove { if remove {
_, err = rdb.SRem(ctx, key, members...).Result() _, err = rdb.SRem(ctx, key, members...).Result()
} else { } else {
@ -1011,13 +1034,14 @@ func (c *connectionService) SetSetItem(connName string, db int, key string, remo
} }
// UpdateSetItem replace member of set // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
_, _ = rdb.SRem(ctx, key, value).Result() _, _ = rdb.SRem(ctx, key, value).Result()
_, err = rdb.SAdd(ctx, key, newValue).Result() _, err = rdb.SAdd(ctx, key, newValue).Result()
if err != nil { if err != nil {
@ -1030,13 +1054,14 @@ func (c *connectionService) UpdateSetItem(connName string, db int, key, value, n
} }
// UpdateZSetValue update value of sorted set member // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
updated := map[string]any{} updated := map[string]any{}
var removed []string var removed []string
if len(newValue) <= 0 { if len(newValue) <= 0 {
@ -1080,13 +1105,14 @@ func (c *connectionService) UpdateZSetValue(connName string, db int, key, value,
} }
// AddZSetValue add item to sorted set // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
members := maputil.ToSlice(valueScore, func(k string) redis.Z { members := maputil.ToSlice(valueScore, func(k string) redis.Z {
return redis.Z{ return redis.Z{
Score: valueScore[k], Score: valueScore[k],
@ -1112,13 +1138,14 @@ func (c *connectionService) AddZSetValue(connName string, db int, key string, ac
} }
// AddStreamValue add stream field // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
_, err = rdb.XAdd(ctx, &redis.XAddArgs{ _, err = rdb.XAdd(ctx, &redis.XAddArgs{
Stream: key, Stream: key,
ID: ID, ID: ID,
@ -1134,26 +1161,28 @@ func (c *connectionService) AddStreamValue(connName string, db int, key, ID stri
} }
// RemoveStreamValues remove stream values by id // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
_, err = rdb.XDel(ctx, key, IDs...).Result() _, err = rdb.XDel(ctx, key, IDs...).Result()
resp.Success = true resp.Success = true
return return
} }
// SetKeyTTL set ttl of key // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
key := strutil.DecodeRedisKey(k)
var expiration time.Duration var expiration time.Duration
if ttl < 0 { if ttl < 0 {
if err = rdb.Persist(ctx, key).Err(); err != nil { if err = rdb.Persist(ctx, key).Err(); err != nil {
@ -1173,14 +1202,15 @@ func (c *connectionService) SetKeyTTL(connName string, db int, key string, ttl i
} }
// DeleteKey remove redis key // 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) rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
var deletedKeys []string
key := strutil.DecodeRedisKey(k)
var deletedKeys []string
if strings.HasSuffix(key, "*") { if strings.HasSuffix(key, "*") {
// delete by prefix // delete by prefix
var cursor uint64 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

@ -54,7 +54,7 @@ func ConvertTo(str, targetType string) (value, resultType string) {
return return
case types.HEX: case types.HEX:
if hexStr, ok := decodeHex(str); ok { if hexStr, ok := decodeToHex(str); ok {
value = hexStr value = hexStr
} else { } else {
value = str value = str
@ -161,8 +161,8 @@ func autoToType(str string) (value, resultType string) {
return return
} }
if isBinary(str) { if containsBinary(str) {
if value, ok = decodeHex(str); ok { if value, ok = decodeToHex(str); ok {
resultType = types.HEX resultType = types.HEX
return return
} }
@ -174,24 +174,12 @@ func autoToType(str string) (value, resultType string) {
return return
} }
func isBinary(str string) bool {
for _, s := range str {
if s < 0x20 || s > 0x7E {
return true
}
}
return false
}
func decodeJson(str string) (string, bool) { func decodeJson(str string) (string, bool) {
var data any
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) || if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) { (strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
if err := json.Unmarshal([]byte(str), &data); err == nil { var out bytes.Buffer
var jsonByte []byte if err := json.Indent(&out, []byte(str), "", " "); err == nil {
if jsonByte, err = json.MarshalIndent(data, "", " "); err == nil { return out.String(), true
return string(jsonByte), true
}
} }
} }
return str, false return str, false
@ -212,7 +200,7 @@ func decodeBinary(str string) (string, bool) {
return binary.String(), true return binary.String(), true
} }
func decodeHex(str string) (string, bool) { func decodeToHex(str string) (string, bool) {
decodeStr := hex.EncodeToString([]byte(str)) decodeStr := hex.EncodeToString([]byte(str))
var resultStr strings.Builder var resultStr strings.Builder
for i := 0; i < len(decodeStr); i += 2 { 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> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { typesBgColor, typesColor, validType } from '@/consts/support_redis_type.js' import { typesBgColor, typesColor, validType } from '@/consts/support_redis_type.js'
import Binary from '@/components/icons/Binary.vue'
const props = defineProps({ const props = defineProps({
type: { type: {
@ -10,6 +11,7 @@ const props = defineProps({
}, },
default: 'STRING', default: 'STRING',
}, },
binaryKey: Boolean,
bordered: Boolean, bordered: Boolean,
size: String, size: String,
}) })
@ -31,6 +33,9 @@ const backgroundColor = computed(() => {
:size="props.size" :size="props.size"
strong> strong>
{{ props.type }} {{ props.type }}
<template #icon>
<n-icon v-if="binaryKey" :component="Binary" size="18" />
</template>
</n-tag> </n-tag>
<!-- <div class="redis-type-tag flex-box-h" :style="{backgroundColor: backgroundColor}">{{ props.type }}</div>--> <!-- <div class="redis-type-tag flex-box-h" :style="{backgroundColor: backgroundColor}">{{ props.type }}</div>-->
</template> </template>

View File

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

View File

@ -11,6 +11,8 @@ import IconButton from '@/components/common/IconButton.vue'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import Copy from '@/components/icons/Copy.vue' import Copy from '@/components/icons/Copy.vue'
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js' import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
import { computed } from 'vue'
import { isEmpty } from 'lodash'
const props = defineProps({ const props = defineProps({
server: String, server: String,
@ -23,6 +25,10 @@ const props = defineProps({
default: 'STRING', default: 'STRING',
}, },
keyPath: String, keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: { ttl: {
type: Number, type: Number,
default: -1, default: -1,
@ -33,8 +39,20 @@ const dialogStore = useDialog()
const connectionStore = useConnectionStore() const connectionStore = useConnectionStore()
const i18n = useI18n() 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 = () => { const onReloadKey = () => {
connectionStore.loadKeyValue(props.server, props.db, props.keyPath) connectionStore.loadKeyValue(props.server, props.db, keyName.value)
} }
const onCopyKey = () => { 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 = () => { const onDeleteKey = () => {
$dialog.warning(i18n.t('dialogue.remove_tip', { name: props.keyPath }), () => { $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) { if (success) {
$message.success(i18n.t('dialogue.delete_key_succ', { key: props.keyPath })) $message.success(i18n.t('dialogue.delete_key_succ', { key: props.keyPath }))
} }
@ -63,7 +89,7 @@ const onDeleteKey = () => {
<template> <template>
<div class="content-toolbar flex-box-h"> <div class="content-toolbar flex-box-h">
<n-input-group> <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"> <n-input v-model:value="props.keyPath">
<template #suffix> <template #suffix>
<icon-button :icon="Refresh" size="18" t-tooltip="interface.reload" @click="onReloadKey" /> <icon-button :icon="Refresh" size="18" t-tooltip="interface.reload" @click="onReloadKey" />
@ -86,18 +112,13 @@ const onDeleteKey = () => {
</template> </template>
TTL TTL
</n-tooltip> </n-tooltip>
<icon-button <icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="onRenameKey" />
:icon="Edit"
border
size="18"
t-tooltip="interface.rename_key"
@click="dialogStore.openRenameKeyDialog(props.server, props.db, props.keyPath)" />
</n-button-group> </n-button-group>
<n-tooltip> <n-tooltip>
<template #trigger> <template #trigger>
<n-button> <n-button :focusable="false" @click="onDeleteKey">
<template #icon> <template #icon>
<n-icon :component="Delete" size="18" @click="onDeleteKey" /> <n-icon :component="Delete" size="18" />
</template> </template>
</n-button> </n-button>
</template> </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 EditableTableColumn from '@/components/common/EditableTableColumn.vue'
import useDialogStore from 'stores/dialog.js' import useDialogStore from 'stores/dialog.js'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import { isEmpty } from 'lodash'
const i18n = useI18n() const i18n = useI18n()
const props = defineProps({ const props = defineProps({
name: String, name: String,
db: Number, db: Number,
keyPath: String, keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: { ttl: {
type: Number, type: Number,
default: -1, default: -1,
@ -22,6 +27,14 @@ const props = defineProps({
size: Number, size: Number,
}) })
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const filterOption = [ const filterOption = [
{ {
value: 1, value: 1,
@ -119,11 +132,11 @@ const actionColumn = {
const { success, msg } = await connectionStore.removeHashField( const { success, msg } = await connectionStore.removeHashField(
props.name, props.name,
props.db, props.db,
props.keyPath, keyName.value,
row.key, row.key,
) )
if (success) { 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 })) $message.success(i18n.t('dialogue.delete_key_succ', { key: row.key }))
// update display value // update display value
// if (!isEmpty(removed)) { // if (!isEmpty(removed)) {
@ -143,13 +156,13 @@ const actionColumn = {
const { success, msg } = await connectionStore.setHash( const { success, msg } = await connectionStore.setHash(
props.name, props.name,
props.db, props.db,
props.keyPath, keyName.value,
row.key, row.key,
currentEditRow.value.key, currentEditRow.value.key,
currentEditRow.value.value, currentEditRow.value.value,
) )
if (success) { 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')) $message.success(i18n.t('dialogue.save_value_succ'))
// update display value // update display value
// if (!isEmpty(updated)) { // if (!isEmpty(updated)) {
@ -198,7 +211,7 @@ const tableData = computed(() => {
return data return data
}) })
const onAddRow = () => { 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('') const filterValue = ref('')
@ -240,7 +253,13 @@ const onUpdateFilter = (filters, sourceColumn) => {
<template> <template>
<div class="content-wrapper flex-box-v"> <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="tb2 flex-box-h">
<div class="flex-box-h"> <div class="flex-box-h">
<n-input-group> <n-input-group>

View File

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

View File

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

View File

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

View File

@ -15,6 +15,10 @@ const props = defineProps({
name: String, name: String,
db: Number, db: Number,
keyPath: String, keyPath: String,
keyCode: {
type: Array,
default: null,
},
ttl: { ttl: {
type: Number, type: Number,
default: -1, default: -1,
@ -23,6 +27,14 @@ const props = defineProps({
size: Number, size: Number,
}) })
/**
*
* @type {ComputedRef<string|number[]>}
*/
const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})
const filterOption = [ const filterOption = [
{ {
value: 1, value: 1,
@ -149,11 +161,11 @@ const actionColumn = {
const { success, msg } = await connectionStore.removeZSetItem( const { success, msg } = await connectionStore.removeZSetItem(
props.name, props.name,
props.db, props.db,
props.keyPath, keyName.value,
row.value, row.value,
) )
if (success) { 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 })) $message.success(i18n.t('dialogue.delete_key_succ', { key: row.value }))
} else { } else {
$message.error(msg) $message.error(msg)
@ -172,13 +184,13 @@ const actionColumn = {
const { success, msg } = await connectionStore.updateZSetItem( const { success, msg } = await connectionStore.updateZSetItem(
props.name, props.name,
props.db, props.db,
props.keyPath, keyName.value,
row.value, row.value,
newValue, newValue,
currentEditRow.value.score, currentEditRow.value.score,
) )
if (success) { 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')) $message.success(i18n.t('dialogue.save_value_succ'))
} else { } else {
$message.error(msg) $message.error(msg)
@ -222,7 +234,7 @@ const tableData = computed(() => {
}) })
const onAddRow = () => { 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('') const filterValue = ref('')
@ -266,7 +278,13 @@ const onUpdateFilter = (filters, sourceColumn) => {
<template> <template>
<div class="content-wrapper flex-box-v"> <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="tb2 flex-box-h">
<div class="flex-box-h"> <div class="flex-box-h">
<n-input-group> <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 AddZSetValue from '@/components/new_value/AddZSetValue.vue'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import NewStreamValue from '@/components/new_value/NewStreamValue.vue' import NewStreamValue from '@/components/new_value/NewStreamValue.vue'
import { size, slice } from 'lodash' import { isEmpty, size, slice } from 'lodash'
const i18n = useI18n() const i18n = useI18n()
const newForm = reactive({ const newForm = reactive({
server: '', server: '',
db: 0, db: 0,
key: '', key: '',
keyCode: null,
type: '', type: '',
opType: 0, opType: 0,
value: null, value: null,
@ -65,10 +66,11 @@ watch(
() => dialogStore.addFieldsDialogVisible, () => dialogStore.addFieldsDialogVisible,
(visible) => { (visible) => {
if (visible) { if (visible) {
const { server, db, key, type } = dialogStore.addFieldParam const { server, db, key, keyCode, type } = dialogStore.addFieldParam
newForm.server = server newForm.server = server
newForm.db = db newForm.db = db
newForm.key = key newForm.key = key
newForm.keyCode = keyCode
newForm.type = type newForm.type = type
newForm.opType = 0 newForm.opType = 0
newForm.value = null newForm.value = null
@ -79,24 +81,25 @@ watch(
const connectionStore = useConnectionStore() const connectionStore = useConnectionStore()
const onAdd = async () => { const onAdd = async () => {
try { try {
const { server, db, key, type } = newForm const { server, db, key, keyCode, type } = newForm
let { value } = newForm let { value } = newForm
if (value == null) { if (value == null) {
value = defaultValue[type] value = defaultValue[type]
} }
const keyName = isEmpty(keyCode) ? key : keyCode
switch (type) { switch (type) {
case types.LIST: case types.LIST:
{ {
let data let data
if (newForm.opType === 1) { if (newForm.opType === 1) {
data = await connectionStore.prependListItem(server, db, key, value) data = await connectionStore.prependListItem(server, db, keyName, value)
} else { } else {
data = await connectionStore.appendListItem(server, db, key, value) data = await connectionStore.appendListItem(server, db, keyName, value)
} }
const { success, msg } = data const { success, msg } = data
if (success) { if (success) {
if (newForm.reload) { if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {}) connectionStore.loadKeyValue(server, db, keyName).then(() => {})
} }
$message.success(i18n.t('dialogue.handle_succ')) $message.success(i18n.t('dialogue.handle_succ'))
} else { } else {
@ -107,10 +110,16 @@ const onAdd = async () => {
case types.HASH: 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 (success) {
if (newForm.reload) { if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {}) connectionStore.loadKeyValue(server, db, keyName).then(() => {})
} }
$message.success(i18n.t('dialogue.handle_succ')) $message.success(i18n.t('dialogue.handle_succ'))
} else { } else {
@ -121,10 +130,10 @@ const onAdd = async () => {
case types.SET: 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 (success) {
if (newForm.reload) { if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {}) connectionStore.loadKeyValue(server, db, keyName).then(() => {})
} }
$message.success(i18n.t('dialogue.handle_succ')) $message.success(i18n.t('dialogue.handle_succ'))
} else { } else {
@ -135,10 +144,16 @@ const onAdd = async () => {
case types.ZSET: 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 (success) {
if (newForm.reload) { if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {}) connectionStore.loadKeyValue(server, db, keyName).then(() => {})
} }
$message.success(i18n.t('dialogue.handle_succ')) $message.success(i18n.t('dialogue.handle_succ'))
} else { } else {
@ -153,13 +168,13 @@ const onAdd = async () => {
const { success, msg } = await connectionStore.addStreamValue( const { success, msg } = await connectionStore.addStreamValue(
server, server,
db, db,
key, keyName,
value[0], value[0],
slice(value, 1), slice(value, 1),
) )
if (success) { if (success) {
if (newForm.reload) { if (newForm.reload) {
connectionStore.loadKeyValue(server, db, key).then(() => {}) connectionStore.loadKeyValue(server, db, keyName).then(() => {})
} }
$message.success(i18n.t('dialogue.handle_succ')) $message.success(i18n.t('dialogue.handle_succ'))
} else { } else {

View File

@ -3,8 +3,14 @@ import { reactive, ref, watch } from 'vue'
import useDialog from 'stores/dialog' import useDialog from 'stores/dialog'
import useTabStore from 'stores/tab.js' import useTabStore from 'stores/tab.js'
import useConnectionStore from 'stores/connections.js' import useConnectionStore from 'stores/connections.js'
import Binary from '@/components/icons/Binary.vue'
import { isEmpty } from 'lodash'
const ttlForm = reactive({ const ttlForm = reactive({
server: '',
db: 0,
key: '',
keyCode: null,
ttl: -1, ttl: -1,
}) })
@ -12,9 +18,6 @@ const dialogStore = useDialog()
const connectionStore = useConnectionStore() const connectionStore = useConnectionStore()
const tabStore = useTabStore() const tabStore = useTabStore()
const currentServer = ref('')
const currentKey = ref('')
const currentDB = ref(0)
watch( watch(
() => dialogStore.ttlDialogVisible, () => dialogStore.ttlDialogVisible,
(visible) => { (visible) => {
@ -22,15 +25,15 @@ watch(
// get ttl from current tab // get ttl from current tab
const tab = tabStore.currentTab const tab = tabStore.currentTab
if (tab != null) { if (tab != null) {
ttlForm.server = tab.name
ttlForm.db = tab.db
ttlForm.key = tab.key ttlForm.key = tab.key
ttlForm.keyCode = tab.keyCode
if (tab.ttl < 0) { if (tab.ttl < 0) {
// forever // forever
} else { } else {
ttlForm.ttl = tab.ttl 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) { if (tab == null) {
return 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) { if (success) {
tabStore.updateTTL({ tabStore.updateTTL({
server: currentServer.value, server: ttlForm.server,
db: currentDB.value, db: ttlForm.db,
key: currentKey.value, key: ttlForm.key,
ttl: ttlForm.ttl, ttl: ttlForm.ttl,
}) })
} }
} catch (e) { } catch (e) {
$message.error(e.message || 'set ttl fail')
} finally { } finally {
dialogStore.closeTTLDialog() dialogStore.closeTTLDialog()
} }
@ -74,7 +79,11 @@ const onConfirm = async () => {
transform-origin="center"> transform-origin="center">
<n-form :model="ttlForm" :show-require-mark="false" label-placement="top"> <n-form :model="ttlForm" :show-require-mark="false" label-placement="top">
<n-form-item :label="$t('common.key')"> <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>
<n-form-item :label="$t('interface.ttl')" required> <n-form-item :label="$t('interface.ttl')" required>
<n-input-number <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 { ConnectionType } from '@/consts/connection_type.js'
import { NIcon, NSpace, NTag } from 'naive-ui' import { NIcon, NSpace, NTag } from 'naive-ui'
import Key from '@/components/icons/Key.vue' import Key from '@/components/icons/Key.vue'
import Binary from '@/components/icons/Binary.vue'
import ToggleDb from '@/components/icons/ToggleDb.vue' import ToggleDb from '@/components/icons/ToggleDb.vue'
import { find, get, includes, indexOf, isEmpty, pull, remove, size } from 'lodash' import { find, get, includes, indexOf, isEmpty, pull, remove, size } from 'lodash'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@ -223,7 +224,9 @@ const handleSelectContextMenu = (key) => {
return return
} }
const node = connectionStore.getNode(selectedKey) 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) { switch (key) {
case 'server_info': case 'server_info':
tabStore.setSelectedKeys(props.server) tabStore.setSelectedKeys(props.server)
@ -267,10 +270,10 @@ const handleSelectContextMenu = (key) => {
dialogStore.openDeleteKeyDialog(props.server, db, isEmpty(redisKey) ? '*' : redisKey + ':*') dialogStore.openDeleteKeyDialog(props.server, db, isEmpty(redisKey) ? '*' : redisKey + ':*')
break break
case 'value_remove': 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) => { connectionStore.deleteKey(props.server, db, redisKey).then((success) => {
if (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 // prevent load duplicate key
for (const node of options) { for (const node of options) {
if (node.type === ConnectionType.RedisValue) { 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)) { if (!includes(selectedKeys.value, key)) {
connectionStore.loadKeyValue(props.server, db, redisKey) connectionStore.loadKeyValue(props.server, db, redisKey)
} }
@ -373,7 +377,7 @@ const renderPrefix = ({ option }) => {
NIcon, NIcon,
{ size: 20 }, { size: 20 },
{ {
default: () => h(Key), default: () => h(!!option.redisKeyCode ? Binary : Key),
}, },
) )
} }

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import {
get, get,
isEmpty, isEmpty,
join, join,
map,
remove, remove,
size, size,
slice, slice,
@ -49,6 +50,7 @@ import {
import { ConnectionType } from '@/consts/connection_type.js' import { ConnectionType } from '@/consts/connection_type.js'
import useTabStore from './tab.js' import useTabStore from './tab.js'
import { types } from '@/consts/support_redis_type.js' import { types } from '@/consts/support_redis_type.js'
import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js'
const useConnectionStore = defineStore('connections', { const useConnectionStore = defineStore('connections', {
/** /**
@ -68,6 +70,7 @@ const useConnectionStore = defineStore('connections', {
* @property {number} type * @property {number} type
* @property {number} [db] - database index, type == ConnectionType.RedisDB only * @property {number} [db] - database index, type == ConnectionType.RedisDB only
* @property {string} [redisKey] - redis key, type == ConnectionType.RedisKey || type == ConnectionType.RedisValue 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 {number} [keys] - children key count
* @property {boolean} [isLeaf] * @property {boolean} [isLeaf]
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only * @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
@ -529,11 +532,10 @@ const useConnectionStore = defineStore('connections', {
selDB.opened = true selDB.opened = true
if (isEmpty(keys)) { if (isEmpty(keys)) {
selDB.children = [] selDB.children = []
return } else {
// append db node to current connection's children
this._addKeyNodes(connName, db, keys)
} }
// append db node to current connection's children
this._addKeyNodes(connName, db, keys)
this._tidyNode(connName, db) this._tidyNode(connName, db)
}, },
@ -592,7 +594,7 @@ const useConnectionStore = defineStore('connections', {
* load redis key * load redis key
* @param {string} server * @param {string} server
* @param {number} db * @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] * @param {string} [viewType]
*/ */
async loadKeyValue(server, db, key, viewType) { async loadKeyValue(server, db, key, viewType) {
@ -602,12 +604,15 @@ const useConnectionStore = defineStore('connections', {
const { data, success, msg } = await GetKeyValue(server, db, key, viewType) const { data, success, msg } = await GetKeyValue(server, db, key, viewType)
if (success) { if (success) {
const { type, ttl, value, size, viewAs } = data const { type, ttl, value, size, viewAs } = data
const k = decodeRedisKey(key)
const binaryKey = k !== key
tab.upsertTab({ tab.upsertTab({
server, server,
db, db,
type, type,
ttl, ttl,
key, keyCode: binaryKey ? key : undefined,
key: k,
value, value,
size, size,
viewAs, viewAs,
@ -629,6 +634,7 @@ const useConnectionStore = defineStore('connections', {
type: 'none', type: 'none',
ttl: -1, ttl: -1,
key: null, key: null,
keyCode: null,
value: null, value: null,
size: 0, size: 0,
}) })
@ -713,7 +719,7 @@ const useConnectionStore = defineStore('connections', {
* remove keys in db * remove keys in db
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string[]} keys * @param {Array<string|number[]>} keys
* @param {boolean} [sortInsert] * @param {boolean} [sortInsert]
* @return {{success: boolean, newKey: number, newLayer: number, replaceKey: number}} * @return {{success: boolean, newKey: number, newLayer: number, replaceKey: number}}
* @private * @private
@ -735,7 +741,9 @@ const useConnectionStore = defineStore('connections', {
const nodeMap = this._getNodeMap(connName, db) const nodeMap = this._getNodeMap(connName, db)
const rootChildren = selDB.children const rootChildren = selDB.children
for (const key of keys) { 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 len = size(keyParts)
const lastIdx = len - 1 const lastIdx = len - 1
let handlePath = '' let handlePath = ''
@ -776,10 +784,11 @@ const useConnectionStore = defineStore('connections', {
const replaceKey = nodeMap.has(nodeKey) const replaceKey = nodeMap.has(nodeKey)
const selectedNode = { const selectedNode = {
key: `${connName}/db${db}#${nodeKey}`, key: `${connName}/db${db}#${nodeKey}`,
label: keyParts[i], label: binaryKey ? k : keyParts[i],
db, db,
keys: 0, keys: 0,
redisKey: handlePath, redisKey: handlePath,
redisKeyCode: binaryKey ? key : undefined,
type: ConnectionType.RedisValue, type: ConnectionType.RedisValue,
isLeaf: true, isLeaf: true,
} }
@ -864,7 +873,7 @@ const useConnectionStore = defineStore('connections', {
/** /**
* sort all node item's children and calculate keys count * sort all node item's children and calculate keys count
* @param node * @param {DatabaseItem} node
* @param {boolean} skipSort skip sorting children * @param {boolean} skipSort skip sorting children
* @returns {boolean} return whether key count changed * @returns {boolean} return whether key count changed
* @private * @private
@ -881,7 +890,12 @@ const useConnectionStore = defineStore('connections', {
count += elem.keys count += elem.keys
} }
} else { } else {
count += 1 if (node.type === ConnectionType.RedisValue) {
count += 1
} else {
// no children in db node or layer node, set count to 0
count = 0
}
} }
if (node.keys !== count) { if (node.keys !== count) {
node.keys = count node.keys = count
@ -926,7 +940,7 @@ const useConnectionStore = defineStore('connections', {
* set redis key * set redis key
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {string} keyType * @param {string} keyType
* @param {any} value * @param {any} value
* @param {number} ttl * @param {number} ttl
@ -959,7 +973,7 @@ const useConnectionStore = defineStore('connections', {
* when both field and newField are set, and field !== newField, delete field and add newField * when both field and newField are set, and field !== newField, delete field and add newField
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {string} field * @param {string} field
* @param {string} newField * @param {string} newField
* @param {string} value * @param {string} value
@ -983,7 +997,7 @@ const useConnectionStore = defineStore('connections', {
* insert or update hash field item * insert or update hash field item
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields * @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields
* @param {string[]} fieldItems field1, value1, filed2, value2... * @param {string[]} fieldItems field1, value1, filed2, value2...
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>} * @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
@ -1028,7 +1042,7 @@ const useConnectionStore = defineStore('connections', {
* insert list item * insert list item
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {int} action 0: push to head, 1: push to tail * @param {int} action 0: push to head, 1: push to tail
* @param {string[]}values * @param {string[]}values
* @returns {Promise<*|{msg, success: boolean}>} * @returns {Promise<*|{msg, success: boolean}>}
@ -1089,7 +1103,7 @@ const useConnectionStore = defineStore('connections', {
* update value of list item by index * update value of list item by index
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {number} index * @param {number} index
* @param {string} value * @param {string} value
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>} * @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
@ -1112,7 +1126,7 @@ const useConnectionStore = defineStore('connections', {
* remove list item * remove list item
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {number} index * @param {number} index
* @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>} * @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>}
*/ */
@ -1134,7 +1148,7 @@ const useConnectionStore = defineStore('connections', {
* add item to set * add item to set
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number} key
* @param {string} value * @param {string} value
* @returns {Promise<{[msg]: string, success: boolean}>} * @returns {Promise<{[msg]: string, success: boolean}>}
*/ */
@ -1155,7 +1169,7 @@ const useConnectionStore = defineStore('connections', {
* update value of set item * update value of set item
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {string} value * @param {string} value
* @param {string} newValue * @param {string} newValue
* @returns {Promise<{[msg]: string, success: boolean}>} * @returns {Promise<{[msg]: string, success: boolean}>}
@ -1175,10 +1189,10 @@ const useConnectionStore = defineStore('connections', {
/** /**
* remove item from set * remove item from set
* @param connName * @param {string} connName
* @param db * @param {number} db
* @param key * @param {string|number[]} key
* @param value * @param {string} value
* @returns {Promise<{[msg]: string, success: boolean}>} * @returns {Promise<{[msg]: string, success: boolean}>}
*/ */
async removeSetItem(connName, db, key, value) { async removeSetItem(connName, db, key, value) {
@ -1198,7 +1212,7 @@ const useConnectionStore = defineStore('connections', {
* add item to sorted set * add item to sorted set
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {number} action * @param {number} action
* @param {Object.<string, number>} vs value: score * @param {Object.<string, number>} vs value: score
* @returns {Promise<{[msg]: string, success: boolean}>} * @returns {Promise<{[msg]: string, success: boolean}>}
@ -1220,7 +1234,7 @@ const useConnectionStore = defineStore('connections', {
* update item of sorted set * update item of sorted set
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {string} value * @param {string} value
* @param {string} newValue * @param {string} newValue
* @param {number} score * @param {number} score
@ -1244,7 +1258,7 @@ const useConnectionStore = defineStore('connections', {
* remove item from sorted set * remove item from sorted set
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param key * @param {string|number[]} key
* @param {string} value * @param {string} value
* @returns {Promise<{[msg]: string, success: boolean, [removed]: []}>} * @returns {Promise<{[msg]: string, success: boolean, [removed]: []}>}
*/ */
@ -1266,7 +1280,7 @@ const useConnectionStore = defineStore('connections', {
* insert new stream field item * insert new stream field item
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {string} id * @param {string} id
* @param {string[]} values field1, value1, filed2, value2... * @param {string[]} values field1, value1, filed2, value2...
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>} * @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
@ -1289,7 +1303,7 @@ const useConnectionStore = defineStore('connections', {
* remove stream field * remove stream field
* @param {string} connName * @param {string} connName
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {string[]|string} ids * @param {string[]|string} ids
* @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>} * @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>}
*/ */
@ -1427,7 +1441,7 @@ const useConnectionStore = defineStore('connections', {
* delete redis key * delete redis key
* @param {string} connName * @param {string} connName
* @param {number} db * @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 * @param {boolean} [soft] do not try to remove from redis if true, just remove from tree data
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
@ -1437,9 +1451,10 @@ const useConnectionStore = defineStore('connections', {
await DeleteKey(connName, db, key) await DeleteKey(connName, db, key)
} }
const k = nativeRedisKey(key)
// update tree view data // update tree view data
this._deleteKeyNode(connName, db, key) this._deleteKeyNode(connName, db, k)
this._tidyNode(connName, db, key, true) this._tidyNode(connName, db, k, true)
// set tab content empty // set tab content empty
const tab = useTabStore() const tab = useTabStore()

View File

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

View File

@ -14,6 +14,7 @@ const useTabStore = defineStore('tab', {
* @property {string} [server] server name * @property {string} [server] server name
* @property {int} [db] database index * @property {int} [db] database index
* @property {string} [key] current key name * @property {string} [key] current key name
* @property {number[]|null|undefined} [keyCode] current key name as char array
* @property {int} [ttl] ttl of current key * @property {int} [ttl] ttl of current key
*/ */
@ -83,11 +84,12 @@ const useTabStore = defineStore('tab', {
* @param {number} [type] * @param {number} [type]
* @param {number} [ttl] * @param {number} [ttl]
* @param {string} [key] * @param {string} [key]
* @param {string} [keyCode]
* @param {number} [size] * @param {number} [size]
* @param {*} [value] * @param {*} [value]
* @param {string} [viewAs] * @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 }) let tabIndex = findIndex(this.tabList, { name: server })
if (tabIndex === -1) { if (tabIndex === -1) {
this.tabList.push({ this.tabList.push({
@ -97,6 +99,7 @@ const useTabStore = defineStore('tab', {
type, type,
ttl, ttl,
key, key,
keyCode,
size, size,
value, value,
viewAs, viewAs,
@ -112,6 +115,7 @@ const useTabStore = defineStore('tab', {
tab.type = type tab.type = type
tab.ttl = ttl tab.ttl = ttl
tab.key = key tab.key = key
tab.keyCode = keyCode
tab.size = size tab.size = size
tab.value = value tab.value = value
tab.viewAs = viewAs tab.viewAs = viewAs
@ -123,7 +127,7 @@ const useTabStore = defineStore('tab', {
* update ttl by tag * update ttl by tag
* @param {string} server * @param {string} server
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {number} ttl * @param {number} ttl
*/ */
updateTTL({ server, db, key, 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": { "info": {
"companyName": "Tiny Craft", "companyName": "Tiny Craft",
"productName": "Tiny RDM", "productName": "Tiny RDM",
"productVersion": "0.9.0", "productVersion": "1.0.0",
"copyright": "Copyright © 2023", "copyright": "Copyright © 2023",
"comments": "Tiny Redis Desktop Manager" "comments": "Tiny Redis Desktop Manager"
} }