feat: add partial entries loading for complex type(list/hash/set/zset/stream) #70
refactor: split "key value loading" into "key summary loading" and "key detail loading"
This commit is contained in:
parent
f2ebd7f358
commit
9618990de8
|
@ -29,11 +29,21 @@ type slowLogItem struct {
|
||||||
Cost int64 `json:"cost"`
|
Cost int64 `json:"cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type entryCursor struct {
|
||||||
|
DB int
|
||||||
|
Type string
|
||||||
|
Key string
|
||||||
|
Pattern string
|
||||||
|
Cursor uint64
|
||||||
|
XLast string // last stream pos
|
||||||
|
}
|
||||||
|
|
||||||
type connectionItem struct {
|
type connectionItem struct {
|
||||||
client redis.UniversalClient
|
client redis.UniversalClient
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelFunc context.CancelFunc
|
cancelFunc context.CancelFunc
|
||||||
cursor map[int]uint64 // current cursor of databases
|
cursor map[int]uint64 // current cursor of databases
|
||||||
|
entryCursor map[int]entryCursor // current entry cursor of databases
|
||||||
stepSize int64
|
stepSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +275,7 @@ func (b *browserService) getRedisClient(connName string, db int) (item connectio
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancelFunc: cancelFunc,
|
cancelFunc: cancelFunc,
|
||||||
cursor: map[int]uint64{},
|
cursor: map[int]uint64{},
|
||||||
|
entryCursor: map[int]entryCursor{},
|
||||||
stepSize: int64(selConn.LoadSize),
|
stepSize: int64(selConn.LoadSize),
|
||||||
}
|
}
|
||||||
if item.stepSize <= 0 {
|
if item.stepSize <= 0 {
|
||||||
|
@ -487,6 +498,345 @@ func (b *browserService) LoadAllKeys(connName string, db int, match, keyType str
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetKeySummary get key summary info
|
||||||
|
func (b *browserService) GetKeySummary(param types.KeySummaryParam) (resp types.JSResp) {
|
||||||
|
item, err := b.getRedisClient(param.Server, param.DB)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, ctx := item.client, item.ctx
|
||||||
|
key := strutil.DecodeRedisKey(param.Key)
|
||||||
|
var keyType string
|
||||||
|
var dur time.Duration
|
||||||
|
keyType, err = client.Type(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyType == "none" {
|
||||||
|
resp.Msg = "key not exists"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var data types.KeySummary
|
||||||
|
data.Type = strings.ToLower(keyType)
|
||||||
|
if dur, err = client.TTL(ctx, key).Result(); err != nil {
|
||||||
|
data.TTL = -1
|
||||||
|
} else {
|
||||||
|
if dur < 0 {
|
||||||
|
data.TTL = -1
|
||||||
|
} else {
|
||||||
|
data.TTL = int64(dur.Seconds())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Size, _ = client.MemoryUsage(ctx, key, 0).Result()
|
||||||
|
switch data.Type {
|
||||||
|
case "string":
|
||||||
|
data.Length, _ = client.StrLen(ctx, key).Result()
|
||||||
|
case "list":
|
||||||
|
data.Length, _ = client.LLen(ctx, key).Result()
|
||||||
|
case "hash":
|
||||||
|
data.Length, _ = client.HLen(ctx, key).Result()
|
||||||
|
case "set":
|
||||||
|
data.Length, _ = client.SCard(ctx, key).Result()
|
||||||
|
case "zset":
|
||||||
|
data.Length, _ = client.ZCard(ctx, key).Result()
|
||||||
|
case "stream":
|
||||||
|
data.Length, _ = client.XLen(ctx, key).Result()
|
||||||
|
default:
|
||||||
|
resp.Msg = "unknown key type"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Success = true
|
||||||
|
resp.Data = data
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyDetail get key detail
|
||||||
|
func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JSResp) {
|
||||||
|
item, err := b.getRedisClient(param.Server, param.DB)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client, ctx, entryCors := item.client, item.ctx, item.entryCursor
|
||||||
|
key := strutil.DecodeRedisKey(param.Key)
|
||||||
|
var keyType string
|
||||||
|
keyType, err = client.Type(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyType == "none" {
|
||||||
|
resp.Msg = "key not exists"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var data types.KeyDetail
|
||||||
|
//var cursor uint64
|
||||||
|
matchPattern := param.MatchPattern
|
||||||
|
if len(matchPattern) <= 0 {
|
||||||
|
matchPattern = "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
// define get entry cursor function
|
||||||
|
getEntryCursor := func() (uint64, string) {
|
||||||
|
if entry, ok := entryCors[param.DB]; !ok || entry.Key != key || entry.Pattern != matchPattern {
|
||||||
|
// not the same key or match pattern, reset cursor
|
||||||
|
entry = entryCursor{
|
||||||
|
DB: param.DB,
|
||||||
|
Key: key,
|
||||||
|
Pattern: matchPattern,
|
||||||
|
Cursor: 0,
|
||||||
|
}
|
||||||
|
entryCors[param.DB] = entry
|
||||||
|
return 0, ""
|
||||||
|
} else {
|
||||||
|
return entry.Cursor, entry.XLast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// define set entry cursor function
|
||||||
|
setEntryCursor := func(cursor uint64) {
|
||||||
|
entryCors[param.DB] = entryCursor{
|
||||||
|
DB: param.DB,
|
||||||
|
Type: "",
|
||||||
|
Key: key,
|
||||||
|
Pattern: matchPattern,
|
||||||
|
Cursor: cursor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// define set last stream pos function
|
||||||
|
setEntryXLast := func(last string) {
|
||||||
|
entryCors[param.DB] = entryCursor{
|
||||||
|
DB: param.DB,
|
||||||
|
Type: "",
|
||||||
|
Key: key,
|
||||||
|
Pattern: matchPattern,
|
||||||
|
XLast: last,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(keyType) {
|
||||||
|
case "string":
|
||||||
|
var str string
|
||||||
|
str, err = client.Get(ctx, key).Result()
|
||||||
|
data.Value, data.DecodeType, data.ViewAs = strutil.ConvertTo(str, param.DecodeType, param.ViewAs)
|
||||||
|
|
||||||
|
case "list":
|
||||||
|
loadListHandle := func() ([]string, bool, error) {
|
||||||
|
var items []string
|
||||||
|
var cursor uint64
|
||||||
|
if param.Full {
|
||||||
|
// load all
|
||||||
|
cursor = 0
|
||||||
|
items, err = client.LRange(ctx, key, 0, -1).Result()
|
||||||
|
} else {
|
||||||
|
cursor, _ = getEntryCursor()
|
||||||
|
scanSize := int64(Preferences().GetScanSize())
|
||||||
|
items, err = client.LRange(ctx, key, int64(cursor), int64(cursor)+scanSize-1).Result()
|
||||||
|
cursor = cursor + uint64(scanSize)
|
||||||
|
if len(items) < int(scanSize) {
|
||||||
|
cursor = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEntryCursor(cursor)
|
||||||
|
if err != nil {
|
||||||
|
return items, false, err
|
||||||
|
}
|
||||||
|
return items, cursor == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Value, data.End, err = loadListHandle()
|
||||||
|
|
||||||
|
case "hash":
|
||||||
|
loadHashHandle := func() (map[string]string, bool, error) {
|
||||||
|
items := map[string]string{}
|
||||||
|
scanSize := int64(Preferences().GetScanSize())
|
||||||
|
var loadedVal []string
|
||||||
|
var cursor uint64
|
||||||
|
if param.Full {
|
||||||
|
// load all
|
||||||
|
cursor = 0
|
||||||
|
for {
|
||||||
|
loadedVal, cursor, err = client.HScan(ctx, key, cursor, "*", scanSize).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(loadedVal); i += 2 {
|
||||||
|
items[loadedVal[i]] = loadedVal[i+1]
|
||||||
|
}
|
||||||
|
if cursor == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cursor, _ = getEntryCursor()
|
||||||
|
loadedVal, cursor, err = client.HScan(ctx, key, cursor, matchPattern, scanSize).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(loadedVal); i += 2 {
|
||||||
|
items[loadedVal[i]] = loadedVal[i+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEntryCursor(cursor)
|
||||||
|
return items, cursor == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Value, data.End, err = loadHashHandle()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case "set":
|
||||||
|
loadSetHandle := func() ([]string, bool, error) {
|
||||||
|
var items []string
|
||||||
|
var cursor uint64
|
||||||
|
scanSize := int64(Preferences().GetScanSize())
|
||||||
|
var loadedKey []string
|
||||||
|
if param.Full {
|
||||||
|
// load all
|
||||||
|
cursor = 0
|
||||||
|
for {
|
||||||
|
loadedKey, cursor, err = client.SScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
|
||||||
|
if err != nil {
|
||||||
|
return items, false, err
|
||||||
|
}
|
||||||
|
items = append(items, loadedKey...)
|
||||||
|
if cursor == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cursor, _ = getEntryCursor()
|
||||||
|
loadedKey, cursor, err = client.SScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
|
||||||
|
items = append(items, loadedKey...)
|
||||||
|
}
|
||||||
|
setEntryCursor(cursor)
|
||||||
|
return items, cursor == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Value, data.End, err = loadSetHandle()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case "zset":
|
||||||
|
loadZSetHandle := func() ([]types.ZSetItem, bool, error) {
|
||||||
|
var items []types.ZSetItem
|
||||||
|
var cursor uint64
|
||||||
|
scanSize := int64(Preferences().GetScanSize())
|
||||||
|
var loadedVal []string
|
||||||
|
if param.Full {
|
||||||
|
// load all
|
||||||
|
cursor = 0
|
||||||
|
for {
|
||||||
|
loadedVal, cursor, err = client.ZScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
|
||||||
|
if err != nil {
|
||||||
|
return items, false, err
|
||||||
|
}
|
||||||
|
var score float64
|
||||||
|
for i := 0; i < len(loadedVal); i += 2 {
|
||||||
|
if score, err = strconv.ParseFloat(loadedVal[i+1], 64); err == nil {
|
||||||
|
items = append(items, types.ZSetItem{
|
||||||
|
Value: loadedVal[i],
|
||||||
|
Score: score,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cursor == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cursor, _ = getEntryCursor()
|
||||||
|
loadedVal, cursor, err = client.ZScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
|
||||||
|
var score float64
|
||||||
|
for i := 0; i < len(loadedVal); i += 2 {
|
||||||
|
if score, err = strconv.ParseFloat(loadedVal[i+1], 64); err == nil {
|
||||||
|
items = append(items, types.ZSetItem{
|
||||||
|
Value: loadedVal[i],
|
||||||
|
Score: score,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEntryCursor(cursor)
|
||||||
|
return items, cursor == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Value, data.End, err = loadZSetHandle()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case "stream":
|
||||||
|
loadStreamHandle := func() ([]types.StreamItem, bool, error) {
|
||||||
|
var msgs []redis.XMessage
|
||||||
|
var items []types.StreamItem
|
||||||
|
var last string
|
||||||
|
if param.Full {
|
||||||
|
// load all
|
||||||
|
last = ""
|
||||||
|
msgs, err = client.XRevRange(ctx, key, "+", "-").Result()
|
||||||
|
} else {
|
||||||
|
scanSize := int64(Preferences().GetScanSize())
|
||||||
|
_, last = getEntryCursor()
|
||||||
|
if len(last) <= 0 {
|
||||||
|
last = "+"
|
||||||
|
}
|
||||||
|
if last != "+" {
|
||||||
|
// add 1 more item when continue scan
|
||||||
|
msgs, err = client.XRevRangeN(ctx, key, last, "-", scanSize+1).Result()
|
||||||
|
msgs = msgs[1:]
|
||||||
|
} else {
|
||||||
|
msgs, err = client.XRevRangeN(ctx, key, last, "-", scanSize).Result()
|
||||||
|
}
|
||||||
|
scanCount := len(msgs)
|
||||||
|
if scanCount <= 0 || scanCount < int(scanSize) {
|
||||||
|
last = ""
|
||||||
|
} else if scanCount > 0 {
|
||||||
|
last = msgs[scanCount-1].ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setEntryXLast(last)
|
||||||
|
for _, msg := range msgs {
|
||||||
|
items = append(items, types.StreamItem{
|
||||||
|
ID: msg.ID,
|
||||||
|
Value: msg.Values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return items, false, err
|
||||||
|
}
|
||||||
|
return items, last == "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Value, data.End, err = loadStreamHandle()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Success = true
|
||||||
|
resp.Data = data
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetKeyValue get value by key
|
// GetKeyValue get value by key
|
||||||
func (b *browserService) GetKeyValue(connName string, db int, k any, viewAs, decodeType string) (resp types.JSResp) {
|
func (b *browserService) GetKeyValue(connName string, db int, k any, viewAs, decodeType string) (resp types.JSResp) {
|
||||||
item, err := b.getRedisClient(connName, db)
|
item, err := b.getRedisClient(connName, db)
|
||||||
|
@ -774,6 +1124,7 @@ func (b *browserService) SetHashValue(connName string, db int, k any, field, new
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
var removedField []string
|
var removedField []string
|
||||||
updatedField := map[string]string{}
|
updatedField := map[string]string{}
|
||||||
|
replacedField := map[string]string{}
|
||||||
if len(field) <= 0 {
|
if len(field) <= 0 {
|
||||||
// old filed is empty, add new field
|
// old filed is empty, add new field
|
||||||
_, err = client.HSet(ctx, key, newField, value).Result()
|
_, err = client.HSet(ctx, key, newField, value).Result()
|
||||||
|
@ -795,6 +1146,7 @@ func (b *browserService) SetHashValue(connName string, db int, k any, field, new
|
||||||
_, err = client.HSet(ctx, key, newField, value).Result()
|
_, err = client.HSet(ctx, key, newField, value).Result()
|
||||||
removedField = append(removedField, field)
|
removedField = append(removedField, field)
|
||||||
updatedField[newField] = value
|
updatedField[newField] = value
|
||||||
|
replacedField[field] = newField
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -805,6 +1157,7 @@ func (b *browserService) SetHashValue(connName string, db int, k any, field, new
|
||||||
resp.Data = map[string]any{
|
resp.Data = map[string]any{
|
||||||
"removed": removedField,
|
"removed": removedField,
|
||||||
"updated": updatedField,
|
"updated": updatedField,
|
||||||
|
"replaced": replacedField,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -944,10 +1297,11 @@ func (b *browserService) SetSetItem(connName string, db int, k any, remove bool,
|
||||||
|
|
||||||
client, ctx := item.client, item.ctx
|
client, ctx := item.client, item.ctx
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
|
var affected int64
|
||||||
if remove {
|
if remove {
|
||||||
_, err = client.SRem(ctx, key, members...).Result()
|
affected, err = client.SRem(ctx, key, members...).Result()
|
||||||
} else {
|
} else {
|
||||||
_, err = client.SAdd(ctx, key, members...).Result()
|
affected, err = client.SAdd(ctx, key, members...).Result()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -955,6 +1309,9 @@ func (b *browserService) SetSetItem(connName string, db int, k any, remove bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"affected": affected,
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1003,6 +1360,9 @@ func (b *browserService) UpdateZSetValue(connName string, db int, k any, value,
|
||||||
Score: score,
|
Score: score,
|
||||||
Member: value,
|
Member: value,
|
||||||
}).Result()
|
}).Result()
|
||||||
|
if err == nil {
|
||||||
|
updated[value] = score
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// remove old value and add new one
|
// remove old value and add new one
|
||||||
_, err = client.ZRem(ctx, key, value).Result()
|
_, err = client.ZRem(ctx, key, value).Result()
|
||||||
|
@ -1075,7 +1435,8 @@ func (b *browserService) AddStreamValue(connName string, db int, k any, ID strin
|
||||||
|
|
||||||
client, ctx := item.client, item.ctx
|
client, ctx := item.client, item.ctx
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
_, err = client.XAdd(ctx, &redis.XAddArgs{
|
var updateID string
|
||||||
|
updateID, err = client.XAdd(ctx, &redis.XAddArgs{
|
||||||
Stream: key,
|
Stream: key,
|
||||||
ID: ID,
|
ID: ID,
|
||||||
Values: fieldItems,
|
Values: fieldItems,
|
||||||
|
@ -1086,6 +1447,9 @@ func (b *browserService) AddStreamValue(connName string, db int, k any, ID strin
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"updateID": updateID,
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1099,8 +1463,18 @@ func (b *browserService) RemoveStreamValues(connName string, db int, k any, IDs
|
||||||
|
|
||||||
client, ctx := item.client, item.ctx
|
client, ctx := item.client, item.ctx
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
_, err = client.XDel(ctx, key, IDs...).Result()
|
|
||||||
|
var affected int64
|
||||||
|
affected, err = client.XDel(ctx, key, IDs...).Result()
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"affected": affected,
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,35 @@ type JSResp struct {
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Data any `json:"data,omitempty"`
|
Data any `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeySummaryParam struct {
|
||||||
|
Server string `json:"server"`
|
||||||
|
DB int `json:"db"`
|
||||||
|
Key any `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySummary struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
TTL int64 `json:"ttl"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Length int64 `json:"length"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyDetailParam struct {
|
||||||
|
Server string `json:"server"`
|
||||||
|
DB int `json:"db"`
|
||||||
|
Key any `json:"key"`
|
||||||
|
ViewAs string `json:"viewAs,omitempty"`
|
||||||
|
DecodeType string `json:"decodeType,omitempty"`
|
||||||
|
MatchPattern string `json:"matchPattern,omitempty"`
|
||||||
|
Reset bool `json:"reset"`
|
||||||
|
Full bool `json:"full"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyDetail struct {
|
||||||
|
Value any `json:"value"`
|
||||||
|
Length int64 `json:"length,omitempty"`
|
||||||
|
ViewAs string `json:"viewAs,omitempty"`
|
||||||
|
DecodeType string `json:"decodeType,omitempty"`
|
||||||
|
End bool `json:"end"`
|
||||||
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ const props = defineProps({
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
navMenuWidth: 60,
|
navMenuWidth: 60,
|
||||||
toolbarHeight: 45,
|
toolbarHeight: 38,
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
|
|
|
@ -56,11 +56,13 @@ const tabContent = computed(() => {
|
||||||
length: tab.length || 0,
|
length: tab.length || 0,
|
||||||
viewAs: tab.viewAs,
|
viewAs: tab.viewAs,
|
||||||
decode: tab.decode,
|
decode: tab.decode,
|
||||||
|
end: tab.end,
|
||||||
|
loading: tab.loading === true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const isBlankValue = computed(() => {
|
const isBlankValue = computed(() => {
|
||||||
return tabContent.value.value == null
|
return tabContent.value?.keyPath == null
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedSubTab = computed(() => {
|
const selectedSubTab = computed(() => {
|
||||||
|
@ -133,19 +135,7 @@ watch(
|
||||||
<span>{{ $t('interface.sub_tab.key_detail') }}</span>
|
<span>{{ $t('interface.sub_tab.key_detail') }}</span>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
<content-value-wrapper
|
<content-value-wrapper :blank="isBlankValue" :content="tabContent" />
|
||||||
:blank="isBlankValue"
|
|
||||||
:db="tabContent.db"
|
|
||||||
:decode="tabContent.decode"
|
|
||||||
:key-code="tabContent.keyCode"
|
|
||||||
:key-path="tabContent.keyPath"
|
|
||||||
:length="tabContent.length"
|
|
||||||
:name="tabContent.name"
|
|
||||||
:size="tabContent.size"
|
|
||||||
:ttl="tabContent.ttl"
|
|
||||||
:type="tabContent.type"
|
|
||||||
:value="tabContent.value"
|
|
||||||
:view-as="tabContent.viewAs" />
|
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- cli pane -->
|
<!-- cli pane -->
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { h, onMounted, onUnmounted, reactive, ref } from 'vue'
|
import { h, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
import { debounce, isEmpty, map, size, split } from 'lodash'
|
import { debounce, isEmpty, map, size } from 'lodash'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,7 @@ import IconButton from '@/components/common/IconButton.vue'
|
||||||
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 { computed } from 'vue'
|
||||||
import { isEmpty, padStart } from 'lodash'
|
import { padStart } from 'lodash'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
|
||||||
import useBrowserStore from 'stores/browser.js'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
@ -34,32 +32,18 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: -1,
|
default: -1,
|
||||||
},
|
},
|
||||||
viewAs: {
|
loading: Boolean,
|
||||||
type: String,
|
|
||||||
default: formatTypes.PLAIN_TEXT,
|
|
||||||
},
|
|
||||||
decode: {
|
|
||||||
type: String,
|
|
||||||
default: decodeTypes.NONE,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['reload', 'rename', 'delete'])
|
||||||
|
|
||||||
const dialogStore = useDialog()
|
const dialogStore = useDialog()
|
||||||
const browserStore = useBrowserStore()
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
|
||||||
const binaryKey = computed(() => {
|
const binaryKey = computed(() => {
|
||||||
return !!props.keyCode
|
return !!props.keyCode
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {ComputedRef<string|number[]>}
|
|
||||||
*/
|
|
||||||
const keyName = computed(() => {
|
|
||||||
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
|
|
||||||
})
|
|
||||||
|
|
||||||
const ttlString = computed(() => {
|
const ttlString = computed(() => {
|
||||||
let s = ''
|
let s = ''
|
||||||
if (props.ttl > 0) {
|
if (props.ttl > 0) {
|
||||||
|
@ -77,10 +61,6 @@ const ttlString = computed(() => {
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
|
|
||||||
const onReloadKey = () => {
|
|
||||||
browserStore.loadKeyValue(props.server, props.db, keyName.value, props.viewAs, props.decode)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onCopyKey = () => {
|
const onCopyKey = () => {
|
||||||
ClipboardSetText(props.keyPath)
|
ClipboardSetText(props.keyPath)
|
||||||
.then((succ) => {
|
.then((succ) => {
|
||||||
|
@ -92,33 +72,20 @@ const onCopyKey = () => {
|
||||||
$message.error(e.message)
|
$message.error(e.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 }), () => {
|
|
||||||
browserStore.deleteKey(props.server, props.db, keyName.value).then((success) => {
|
|
||||||
if (success) {
|
|
||||||
$message.success(i18n.t('dialogue.delete_key_succ', { key: props.keyPath }))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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 :binary-key="binaryKey" :type="props.keyType" size="large" />
|
<redis-type-tag :binary-key="binaryKey" :type="props.keyType" size="large" />
|
||||||
<n-input v-model:value="props.keyPath">
|
<n-input v-model:value="props.keyPath" readonly>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<icon-button :icon="Refresh" size="18" t-tooltip="interface.reload" @click="onReloadKey" />
|
<icon-button
|
||||||
|
:icon="Refresh"
|
||||||
|
:loading="props.loading"
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.reload"
|
||||||
|
@click="emit('reload')" />
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
<icon-button :icon="Copy" border size="18" t-tooltip="interface.copy_key" @click="onCopyKey" />
|
<icon-button :icon="Copy" border size="18" t-tooltip="interface.copy_key" @click="onCopyKey" />
|
||||||
|
@ -135,11 +102,11 @@ const onDeleteKey = () => {
|
||||||
</template>
|
</template>
|
||||||
TTL{{ `${ttl > 0 ? ': ' + ttl + $t('common.second') : ''}` }}
|
TTL{{ `${ttl > 0 ? ': ' + ttl + $t('common.second') : ''}` }}
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="onRenameKey" />
|
<icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="emit('rename')" />
|
||||||
</n-button-group>
|
</n-button-group>
|
||||||
<n-tooltip :show-arrow="false">
|
<n-tooltip :show-arrow="false">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button :focusable="false" @click="onDeleteKey">
|
<n-button :focusable="false" @click="emit('delete')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="Delete" size="18" />
|
<n-icon :component="Delete" size="18" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -7,10 +7,13 @@ import { NButton, NCode, NIcon, NInput, useThemeVars } from 'naive-ui'
|
||||||
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'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty, size } from 'lodash'
|
||||||
import bytes from 'bytes'
|
import bytes from 'bytes'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -38,8 +41,12 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: decodeTypes.NONE,
|
default: decodeTypes.NONE,
|
||||||
},
|
},
|
||||||
|
end: Boolean,
|
||||||
|
loading: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ComputedRef<string|number[]>}
|
* @type {ComputedRef<string|number[]>}
|
||||||
|
@ -149,14 +156,7 @@ const actionColumn = {
|
||||||
row.key,
|
row.key,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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
|
|
||||||
// if (!isEmpty(removed)) {
|
|
||||||
// for (const elem of removed) {
|
|
||||||
// delete props.value[elem]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
@ -175,14 +175,7 @@ const actionColumn = {
|
||||||
currentEditRow.value.value,
|
currentEditRow.value.value,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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
|
|
||||||
// if (!isEmpty(updated)) {
|
|
||||||
// for (const key in updated) {
|
|
||||||
// props.value[key] = updated[key]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
@ -223,6 +216,12 @@ const tableData = computed(() => {
|
||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const entries = computed(() => {
|
||||||
|
const len = size(tableData.value)
|
||||||
|
return `${len} / ${Math.max(len, props.length)}`
|
||||||
|
})
|
||||||
|
|
||||||
const onAddRow = () => {
|
const onAddRow = () => {
|
||||||
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.HASH)
|
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.HASH)
|
||||||
}
|
}
|
||||||
|
@ -268,14 +267,16 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
<div class="content-wrapper flex-box-v">
|
<div class="content-wrapper flex-box-v">
|
||||||
<content-toolbar
|
<content-toolbar
|
||||||
:db="props.db"
|
:db="props.db"
|
||||||
:decode="props.decode"
|
|
||||||
:key-code="props.keyCode"
|
:key-code="props.keyCode"
|
||||||
:key-path="props.keyPath"
|
:key-path="props.keyPath"
|
||||||
:key-type="keyType"
|
:key-type="keyType"
|
||||||
|
:loading="props.loading"
|
||||||
:server="props.name"
|
:server="props.name"
|
||||||
:ttl="ttl"
|
:ttl="ttl"
|
||||||
:view-as="props.viewAs"
|
class="value-item-part"
|
||||||
class="value-item-part" />
|
@delete="emit('delete')"
|
||||||
|
@reload="emit('reload')"
|
||||||
|
@rename="emit('rename')" />
|
||||||
<div class="tb2 value-item-part flex-box-h">
|
<div class="tb2 value-item-part flex-box-h">
|
||||||
<div class="flex-box-h">
|
<div class="flex-box-h">
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
|
@ -294,6 +295,22 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
<n-button-group>
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadList"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_more_entries"
|
||||||
|
@click="emit('loadmore')" />
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadAll"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_all_entries"
|
||||||
|
@click="emit('loadall')" />
|
||||||
|
</n-button-group>
|
||||||
<n-button :focusable="false" plain @click="onAddRow">
|
<n-button :focusable="false" plain @click="onAddRow">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="AddLink" size="18" />
|
<n-icon :component="AddLink" size="18" />
|
||||||
|
@ -301,24 +318,25 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
{{ $t('interface.add_row') }}
|
{{ $t('interface.add_row') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-wrapper value-item-part fill-height flex-box-h">
|
<div class="value-wrapper value-item-part flex-box-v flex-item-expand">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:key="(row) => row.no"
|
:key="(row) => row.no"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:bottom-bordered="false"
|
:bottom-bordered="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
|
:loading="props.loading"
|
||||||
:single-column="true"
|
:single-column="true"
|
||||||
:single-line="false"
|
:single-line="false"
|
||||||
|
class="flex-item-expand"
|
||||||
flex-height
|
flex-height
|
||||||
max-height="100%"
|
|
||||||
size="small"
|
size="small"
|
||||||
striped
|
striped
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
@update:filters="onUpdateFilter" />
|
@update:filters="onUpdateFilter" />
|
||||||
</div>
|
</div>
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ props.length }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="!isNaN(props.length)" vertical />
|
<n-divider v-if="!isNaN(props.length)" vertical />
|
||||||
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
|
|
@ -11,6 +11,9 @@ import useDialogStore from 'stores/dialog.js'
|
||||||
import bytes from 'bytes'
|
import bytes from 'bytes'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -38,8 +41,12 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: decodeTypes.NONE,
|
default: decodeTypes.NONE,
|
||||||
},
|
},
|
||||||
|
end: Boolean,
|
||||||
|
loading: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ComputedRef<string|number[]>}
|
* @type {ComputedRef<string|number[]>}
|
||||||
|
@ -106,12 +113,7 @@ const actionColumn = {
|
||||||
row.no - 1,
|
row.no - 1,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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
|
|
||||||
// if (!isEmpty(removed)) {
|
|
||||||
// props.value.splice(removed[0], 1)
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
@ -129,14 +131,7 @@ const actionColumn = {
|
||||||
currentEditRow.value.value,
|
currentEditRow.value.value,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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
|
|
||||||
// if (!isEmpty(updated)) {
|
|
||||||
// for (const key in updated) {
|
|
||||||
// props.value[key] = updated[key]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
@ -178,6 +173,11 @@ const tableData = computed(() => {
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const entries = computed(() => {
|
||||||
|
const len = size(tableData.value)
|
||||||
|
return `${len} / ${Math.max(len, props.length)}`
|
||||||
|
})
|
||||||
|
|
||||||
const onAddValue = (value) => {
|
const onAddValue = (value) => {
|
||||||
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.LIST)
|
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.LIST)
|
||||||
}
|
}
|
||||||
|
@ -200,14 +200,16 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
<div class="content-wrapper flex-box-v">
|
<div class="content-wrapper flex-box-v">
|
||||||
<content-toolbar
|
<content-toolbar
|
||||||
:db="props.db"
|
:db="props.db"
|
||||||
:decode="props.decode"
|
|
||||||
:key-code="props.keyCode"
|
:key-code="props.keyCode"
|
||||||
:key-path="props.keyPath"
|
:key-path="props.keyPath"
|
||||||
:key-type="keyType"
|
:key-type="keyType"
|
||||||
|
:loading="props.loading"
|
||||||
:server="props.name"
|
:server="props.name"
|
||||||
:ttl="ttl"
|
:ttl="ttl"
|
||||||
:view-as="props.viewAs"
|
class="value-item-part"
|
||||||
class="value-item-part" />
|
@delete="emit('delete')"
|
||||||
|
@reload="emit('reload')"
|
||||||
|
@rename="emit('rename')" />
|
||||||
<div class="tb2 value-item-part flex-box-h">
|
<div class="tb2 value-item-part flex-box-h">
|
||||||
<div class="flex-box-h">
|
<div class="flex-box-h">
|
||||||
<n-input
|
<n-input
|
||||||
|
@ -218,6 +220,22 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
@update:value="onFilterInput" />
|
@update:value="onFilterInput" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
<n-button-group>
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadList"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_more_entries"
|
||||||
|
@click="emit('loadmore')" />
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadAll"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_all_entries"
|
||||||
|
@click="emit('loadall')" />
|
||||||
|
</n-button-group>
|
||||||
<n-button :focusable="false" plain @click="onAddValue">
|
<n-button :focusable="false" plain @click="onAddValue">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="AddLink" size="18" />
|
<n-icon :component="AddLink" size="18" />
|
||||||
|
@ -225,24 +243,25 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
{{ $t('interface.add_row') }}
|
{{ $t('interface.add_row') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-wrapper value-item-part fill-height flex-box-h">
|
<div class="value-wrapper value-item-part flex-box-v flex-item-expand">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:key="(row) => row.no"
|
:key="(row) => row.no"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:bottom-bordered="false"
|
:bottom-bordered="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
|
:loading="props.loading"
|
||||||
:single-column="true"
|
:single-column="true"
|
||||||
:single-line="false"
|
:single-line="false"
|
||||||
|
class="flex-item-expand"
|
||||||
flex-height
|
flex-height
|
||||||
max-height="100%"
|
|
||||||
size="small"
|
size="small"
|
||||||
striped
|
striped
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
@update:filters="onUpdateFilter" />
|
@update:filters="onUpdateFilter" />
|
||||||
</div>
|
</div>
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ props.length }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="!isNaN(props.length)" vertical />
|
<n-divider v-if="!isNaN(props.length)" vertical />
|
||||||
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
|
|
@ -11,6 +11,9 @@ import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
|
||||||
import bytes from 'bytes'
|
import bytes from 'bytes'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -38,8 +41,12 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: decodeTypes.NONE,
|
default: decodeTypes.NONE,
|
||||||
},
|
},
|
||||||
|
end: Boolean,
|
||||||
|
loading: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ComputedRef<string|number[]>}
|
* @type {ComputedRef<string|number[]>}
|
||||||
|
@ -107,10 +114,7 @@ const actionColumn = {
|
||||||
row.value,
|
row.value,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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
|
|
||||||
// props.value.splice(row.no - 1, 1)
|
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
@ -128,10 +132,7 @@ const actionColumn = {
|
||||||
currentEditRow.value.value,
|
currentEditRow.value.value,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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
|
|
||||||
// props.value[row.no - 1] = currentEditRow.value.value
|
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
@ -173,6 +174,11 @@ const tableData = computed(() => {
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const entries = computed(() => {
|
||||||
|
const len = size(tableData.value)
|
||||||
|
return `${len} / ${Math.max(len, props.length)}`
|
||||||
|
})
|
||||||
|
|
||||||
const onAddValue = (value) => {
|
const onAddValue = (value) => {
|
||||||
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.SET)
|
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.SET)
|
||||||
}
|
}
|
||||||
|
@ -195,14 +201,16 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
<div class="content-wrapper flex-box-v">
|
<div class="content-wrapper flex-box-v">
|
||||||
<content-toolbar
|
<content-toolbar
|
||||||
:db="props.db"
|
:db="props.db"
|
||||||
:decode="props.decode"
|
|
||||||
:key-code="props.keyCode"
|
:key-code="props.keyCode"
|
||||||
:key-path="props.keyPath"
|
:key-path="props.keyPath"
|
||||||
:key-type="keyType"
|
:key-type="keyType"
|
||||||
|
:loading="props.loading"
|
||||||
:server="props.name"
|
:server="props.name"
|
||||||
:ttl="ttl"
|
:ttl="ttl"
|
||||||
:view-as="props.viewAs"
|
class="value-item-part"
|
||||||
class="value-item-part" />
|
@delete="emit('delete')"
|
||||||
|
@reload="emit('reload')"
|
||||||
|
@rename="emit('rename')" />
|
||||||
<div class="tb2 value-item-part flex-box-h">
|
<div class="tb2 value-item-part flex-box-h">
|
||||||
<div class="flex-box-h">
|
<div class="flex-box-h">
|
||||||
<n-input
|
<n-input
|
||||||
|
@ -213,6 +221,22 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
@update:value="onFilterInput" />
|
@update:value="onFilterInput" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
<n-button-group>
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadList"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_more_entries"
|
||||||
|
@click="emit('loadmore')" />
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadAll"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_all_entries"
|
||||||
|
@click="emit('loadall')" />
|
||||||
|
</n-button-group>
|
||||||
<n-button :focusable="false" plain @click="onAddValue">
|
<n-button :focusable="false" plain @click="onAddValue">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="AddLink" size="18" />
|
<n-icon :component="AddLink" size="18" />
|
||||||
|
@ -220,24 +244,25 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
{{ $t('interface.add_row') }}
|
{{ $t('interface.add_row') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-wrapper value-item-part fill-height flex-box-h">
|
<div class="value-wrapper value-item-part flex-box-v flex-item-expand">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:key="(row) => row.no"
|
:key="(row) => row.no"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:bottom-bordered="false"
|
:bottom-bordered="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
|
:loading="props.loading"
|
||||||
:single-column="true"
|
:single-column="true"
|
||||||
:single-line="false"
|
:single-line="false"
|
||||||
|
class="flex-item-expand"
|
||||||
flex-height
|
flex-height
|
||||||
max-height="100%"
|
|
||||||
size="small"
|
size="small"
|
||||||
striped
|
striped
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
@update:filters="onUpdateFilter" />
|
@update:filters="onUpdateFilter" />
|
||||||
</div>
|
</div>
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ props.length }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="!isNaN(props.length)" vertical />
|
<n-divider v-if="!isNaN(props.length)" vertical />
|
||||||
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
|
|
@ -7,10 +7,13 @@ import { NButton, NCode, NIcon, NInput, useThemeVars } from 'naive-ui'
|
||||||
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'
|
||||||
import { includes, isEmpty, keys, some, values } from 'lodash'
|
import { includes, isEmpty, keys, size, some, values } from 'lodash'
|
||||||
import bytes from 'bytes'
|
import bytes from 'bytes'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -27,7 +30,10 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: -1,
|
default: -1,
|
||||||
},
|
},
|
||||||
value: Object,
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
size: Number,
|
size: Number,
|
||||||
length: Number,
|
length: Number,
|
||||||
viewAs: {
|
viewAs: {
|
||||||
|
@ -38,8 +44,12 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: decodeTypes.NONE,
|
default: decodeTypes.NONE,
|
||||||
},
|
},
|
||||||
|
end: Boolean,
|
||||||
|
loading: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ComputedRef<string|number[]>}
|
* @type {ComputedRef<string|number[]>}
|
||||||
|
@ -115,14 +125,7 @@ const actionColumn = {
|
||||||
row.id,
|
row.id,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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
|
|
||||||
// if (!isEmpty(removed)) {
|
|
||||||
// for (const elem of removed) {
|
|
||||||
// delete props.value[elem]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
@ -137,15 +140,22 @@ const columns = reactive([idColumn, valueColumn, actionColumn])
|
||||||
|
|
||||||
const tableData = computed(() => {
|
const tableData = computed(() => {
|
||||||
const data = []
|
const data = []
|
||||||
|
if (!isEmpty(props.value)) {
|
||||||
for (const elem of props.value) {
|
for (const elem of props.value) {
|
||||||
data.push({
|
data.push({
|
||||||
id: elem.id,
|
id: elem.id,
|
||||||
value: elem.value,
|
value: elem.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const entries = computed(() => {
|
||||||
|
const len = size(tableData.value)
|
||||||
|
return `${len} / ${Math.max(len, props.length)}`
|
||||||
|
})
|
||||||
|
|
||||||
const onAddRow = () => {
|
const onAddRow = () => {
|
||||||
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.STREAM)
|
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.STREAM)
|
||||||
}
|
}
|
||||||
|
@ -180,14 +190,16 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
<div class="content-wrapper flex-box-v">
|
<div class="content-wrapper flex-box-v">
|
||||||
<content-toolbar
|
<content-toolbar
|
||||||
:db="props.db"
|
:db="props.db"
|
||||||
:decode="props.decode"
|
|
||||||
:key-code="props.keyCode"
|
:key-code="props.keyCode"
|
||||||
:key-path="props.keyPath"
|
:key-path="props.keyPath"
|
||||||
:key-type="keyType"
|
:key-type="keyType"
|
||||||
|
:loading="props.loading"
|
||||||
:server="props.name"
|
:server="props.name"
|
||||||
:ttl="ttl"
|
:ttl="ttl"
|
||||||
:view-as="props.viewAs"
|
class="value-item-part"
|
||||||
class="value-item-part" />
|
@delete="emit('delete')"
|
||||||
|
@reload="emit('reload')"
|
||||||
|
@rename="emit('rename')" />
|
||||||
<div class="tb2 value-item-part flex-box-h">
|
<div class="tb2 value-item-part flex-box-h">
|
||||||
<div class="flex-box-h">
|
<div class="flex-box-h">
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
|
@ -206,6 +218,22 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
<n-button-group>
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadList"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_more_entries"
|
||||||
|
@click="emit('loadmore')" />
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadAll"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_all_entries"
|
||||||
|
@click="emit('loadall')" />
|
||||||
|
</n-button-group>
|
||||||
<n-button :focusable="false" plain @click="onAddRow">
|
<n-button :focusable="false" plain @click="onAddRow">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="AddLink" size="18" />
|
<n-icon :component="AddLink" size="18" />
|
||||||
|
@ -213,17 +241,18 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
{{ $t('interface.add_row') }}
|
{{ $t('interface.add_row') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-wrapper value-item-part fill-height flex-box-h">
|
<div class="value-wrapper value-item-part flex-box-v flex-item-expand">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:key="(row) => row.id"
|
:key="(row) => row.id"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:bottom-bordered="false"
|
:bottom-bordered="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
|
:loading="props.loading"
|
||||||
:single-column="true"
|
:single-column="true"
|
||||||
:single-line="false"
|
:single-line="false"
|
||||||
|
class="flex-item-expand"
|
||||||
flex-height
|
flex-height
|
||||||
max-height="100%"
|
|
||||||
size="small"
|
size="small"
|
||||||
striped
|
striped
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
|
@ -231,7 +260,7 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ props.length }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="!isNaN(props.length)" vertical />
|
<n-divider v-if="!isNaN(props.length)" vertical />
|
||||||
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
|
|
@ -43,8 +43,11 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: decodeTypes.NONE,
|
default: decodeTypes.NONE,
|
||||||
},
|
},
|
||||||
|
loading: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['reload', 'rename', 'delete'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ComputedRef<string|number[]>}
|
* @type {ComputedRef<string|number[]>}
|
||||||
|
@ -53,16 +56,6 @@ const keyName = computed(() => {
|
||||||
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
|
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
|
||||||
})
|
})
|
||||||
|
|
||||||
// const viewOption = computed(() =>
|
|
||||||
// map(types, (t) => {
|
|
||||||
// return {
|
|
||||||
// value: t,
|
|
||||||
// label: t,
|
|
||||||
// key: t,
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
// )
|
|
||||||
|
|
||||||
const keyType = redisTypes.STRING
|
const keyType = redisTypes.STRING
|
||||||
const viewLanguage = computed(() => {
|
const viewLanguage = computed(() => {
|
||||||
switch (props.viewAs) {
|
switch (props.viewAs) {
|
||||||
|
@ -74,11 +67,23 @@ const viewLanguage = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const onViewTypeUpdate = (viewType) => {
|
const onViewTypeUpdate = (viewType) => {
|
||||||
browserStore.loadKeyValue(props.name, props.db, keyName.value, viewType, props.decode)
|
browserStore.loadKeyDetail({
|
||||||
|
server: props.name,
|
||||||
|
db: props.db,
|
||||||
|
key: keyName.value,
|
||||||
|
viewType,
|
||||||
|
decodeType: props.decode,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDecodeTypeUpdate = (decodeType) => {
|
const onDecodeTypeUpdate = (decodeType) => {
|
||||||
browserStore.loadKeyValue(props.name, props.db, keyName.value, props.viewAs, decodeType)
|
browserStore.loadKeyDetail({
|
||||||
|
server: props.name,
|
||||||
|
db: props.db,
|
||||||
|
key: keyName.value,
|
||||||
|
viewType: props.viewAs,
|
||||||
|
decodeType,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,7 +131,7 @@ const onSaveValue = async () => {
|
||||||
props.decode,
|
props.decode,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
await browserStore.loadKeyValue(props.name, props.db, keyName.value)
|
await browserStore.loadKeyDetail({ server: props.name, db: props.db, key: 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)
|
||||||
|
@ -144,14 +149,16 @@ const onSaveValue = async () => {
|
||||||
<div class="content-wrapper flex-box-v">
|
<div class="content-wrapper flex-box-v">
|
||||||
<content-toolbar
|
<content-toolbar
|
||||||
:db="props.db"
|
:db="props.db"
|
||||||
:decode="props.decode"
|
|
||||||
:key-code="keyCode"
|
:key-code="keyCode"
|
||||||
:key-path="keyPath"
|
:key-path="keyPath"
|
||||||
:key-type="keyType"
|
:key-type="keyType"
|
||||||
|
:loading="loading"
|
||||||
:server="props.name"
|
:server="props.name"
|
||||||
:ttl="ttl"
|
:ttl="ttl"
|
||||||
:view-as="props.viewAs"
|
class="value-item-part"
|
||||||
class="value-item-part" />
|
@delete="emit('delete')"
|
||||||
|
@reload="emit('reload')"
|
||||||
|
@rename="emit('rename')" />
|
||||||
<div class="tb2 value-item-part flex-box-h">
|
<div class="tb2 value-item-part flex-box-h">
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<n-button-group v-if="!inEdit">
|
<n-button-group v-if="!inEdit">
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
|
||||||
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import ContentValueString from '@/components/content_value/ContentValueString.vue'
|
import ContentValueString from '@/components/content_value/ContentValueString.vue'
|
||||||
import ContentValueHash from '@/components/content_value/ContentValueHash.vue'
|
import ContentValueHash from '@/components/content_value/ContentValueHash.vue'
|
||||||
|
@ -9,37 +8,48 @@ import ContentValueZset from '@/components/content_value/ContentValueZSet.vue'
|
||||||
import ContentValueStream from '@/components/content_value/ContentValueStream.vue'
|
import ContentValueStream from '@/components/content_value/ContentValueStream.vue'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import { computed, onMounted, watch } from 'vue'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
|
import useDialogStore from 'stores/dialog.js'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
const browserStore = useBrowserStore()
|
const browserStore = useBrowserStore()
|
||||||
|
const dialogStore = useDialogStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
blank: Boolean,
|
blank: Boolean,
|
||||||
type: String,
|
content: {
|
||||||
name: String,
|
type: Object,
|
||||||
db: Number,
|
default: {},
|
||||||
keyPath: String,
|
|
||||||
keyCode: {
|
|
||||||
type: Array,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
ttl: {
|
|
||||||
type: Number,
|
|
||||||
default: -1,
|
|
||||||
},
|
|
||||||
value: [String, Object],
|
|
||||||
size: Number,
|
|
||||||
length: Number,
|
|
||||||
viewAs: {
|
|
||||||
type: String,
|
|
||||||
default: formatTypes.PLAIN_TEXT,
|
|
||||||
},
|
|
||||||
decode: {
|
|
||||||
type: String,
|
|
||||||
default: decodeTypes.NONE,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {ComputedRef<{
|
||||||
|
* type:
|
||||||
|
* String, name: String,
|
||||||
|
* db: Number,
|
||||||
|
* keyPath: String,
|
||||||
|
* keyCode: Array,
|
||||||
|
* ttl: Number,
|
||||||
|
* value: [String, Object],
|
||||||
|
* size: Number,
|
||||||
|
* length: Number,
|
||||||
|
* viewAs: String,
|
||||||
|
* decode: String,
|
||||||
|
* end: Boolean
|
||||||
|
* }>}
|
||||||
|
*/
|
||||||
|
const data = computed(() => {
|
||||||
|
return props.content
|
||||||
|
})
|
||||||
|
|
||||||
|
const binaryKey = computed(() => {
|
||||||
|
return !!data.value.keyCode
|
||||||
|
})
|
||||||
|
|
||||||
const valueComponents = {
|
const valueComponents = {
|
||||||
[redisTypes.STRING]: ContentValueString,
|
[redisTypes.STRING]: ContentValueString,
|
||||||
[redisTypes.HASH]: ContentValueHash,
|
[redisTypes.HASH]: ContentValueHash,
|
||||||
|
@ -49,34 +59,103 @@ const valueComponents = {
|
||||||
[redisTypes.STREAM]: ContentValueStream,
|
[redisTypes.STREAM]: ContentValueStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const keyName = computed(() => {
|
||||||
* reload current selection key
|
return !isEmpty(data.value.keyCode) ? data.value.keyCode : data.value.keyPath
|
||||||
* @returns {Promise<null>}
|
})
|
||||||
*/
|
|
||||||
const onReloadKey = async () => {
|
const loadData = async (reset, full) => {
|
||||||
await browserStore.loadKeyValue(props.name, props.db, props.key, props.viewAs)
|
try {
|
||||||
|
const { name, db, view, decodeType, matchPattern } = data.value
|
||||||
|
await browserStore.loadKeyDetail({
|
||||||
|
server: name,
|
||||||
|
db: db,
|
||||||
|
key: keyName.value,
|
||||||
|
viewType: view,
|
||||||
|
decodeType: decodeType,
|
||||||
|
matchPattern: matchPattern,
|
||||||
|
reset: reset === true,
|
||||||
|
full: full === true,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReload = async () => {
|
||||||
|
try {
|
||||||
|
const { name, db, keyCode, keyPath } = data.value
|
||||||
|
await browserStore.reloadKey({ server: name, db, key: keyCode || keyPath })
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRename = () => {
|
||||||
|
const { name, db, keyPath } = data.value
|
||||||
|
if (binaryKey.value) {
|
||||||
|
$message.error(i18n.t('dialogue.rename_binary_key_fail'))
|
||||||
|
} else {
|
||||||
|
dialogStore.openRenameKeyDialog(name, db, keyPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDelete = () => {
|
||||||
|
$dialog.warning(i18n.t('dialogue.remove_tip', { name: props.keyPath }), () => {
|
||||||
|
const { name, db } = data.value
|
||||||
|
browserStore.deleteKey(name, db, keyName.value).then((success) => {
|
||||||
|
if (success) {
|
||||||
|
$message.success(i18n.t('dialogue.delete_key_succ', { key: props.keyPath }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLoadMore = () => {
|
||||||
|
loadData(false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLoadAll = () => {
|
||||||
|
loadData(false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// onReload()
|
||||||
|
loadData(false, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => data.value?.keyPath,
|
||||||
|
() => {
|
||||||
|
// onReload()
|
||||||
|
loadData(false, false)
|
||||||
|
},
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-empty v-if="props.blank" :description="$t('interface.nonexist_tab_content')" class="empty-content">
|
<n-empty v-if="props.blank" :description="$t('interface.nonexist_tab_content')" class="empty-content">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<n-button :focusable="false" @click="onReloadKey">{{ $t('interface.reload') }}</n-button>
|
<n-button :focusable="false" @click="onReload">{{ $t('interface.reload') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-empty>
|
</n-empty>
|
||||||
<keep-alive v-else>
|
<keep-alive v-else>
|
||||||
<component
|
<component
|
||||||
:is="valueComponents[props.type]"
|
:is="valueComponents[data.type]"
|
||||||
:db="props.db"
|
:db="data.db"
|
||||||
:decode="props.decode"
|
:decode="data.decode || decodeTypes.NONE"
|
||||||
:key-code="props.keyCode"
|
:end="data.end"
|
||||||
:key-path="props.keyPath"
|
:key-code="data.keyCode"
|
||||||
:length="props.length"
|
:key-path="data.keyPath"
|
||||||
:name="props.name"
|
:length="data.length"
|
||||||
:size="props.size"
|
:loading="data.loading === true"
|
||||||
:ttl="props.ttl"
|
:name="data.name"
|
||||||
:value="props.value"
|
:size="data.size"
|
||||||
:view-as="props.viewAs" />
|
:ttl="data.ttl"
|
||||||
|
:value="data.value"
|
||||||
|
:view-as="data.viewAs || formatTypes.PLAIN_TEXT"
|
||||||
|
@delete="onDelete"
|
||||||
|
@loadall="onLoadAll"
|
||||||
|
@loadmore="onLoadMore"
|
||||||
|
@reload="onReload"
|
||||||
|
@rename="onRename" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,14 @@ import AddLink from '@/components/icons/AddLink.vue'
|
||||||
import { NButton, NCode, NIcon, NInput, NInputNumber, useThemeVars } from 'naive-ui'
|
import { NButton, NCode, NIcon, NInput, NInputNumber, useThemeVars } from 'naive-ui'
|
||||||
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 { isEmpty } from 'lodash'
|
import { isEmpty, size } from 'lodash'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import bytes from 'bytes'
|
import bytes from 'bytes'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import LoadList from '@/components/icons/LoadList.vue'
|
||||||
|
import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -38,8 +41,12 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: decodeTypes.NONE,
|
default: decodeTypes.NONE,
|
||||||
},
|
},
|
||||||
|
end: Boolean,
|
||||||
|
loading: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ComputedRef<string|number[]>}
|
* @type {ComputedRef<string|number[]>}
|
||||||
|
@ -178,7 +185,6 @@ const actionColumn = {
|
||||||
row.value,
|
row.value,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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)
|
||||||
|
@ -203,7 +209,6 @@ const actionColumn = {
|
||||||
currentEditRow.value.score,
|
currentEditRow.value.score,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
browserStore.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)
|
||||||
|
@ -235,6 +240,7 @@ const columns = reactive([
|
||||||
|
|
||||||
const tableData = computed(() => {
|
const tableData = computed(() => {
|
||||||
const data = []
|
const data = []
|
||||||
|
if (!isEmpty(props.value)) {
|
||||||
let index = 0
|
let index = 0
|
||||||
for (const elem of props.value) {
|
for (const elem of props.value) {
|
||||||
data.push({
|
data.push({
|
||||||
|
@ -243,9 +249,15 @@ const tableData = computed(() => {
|
||||||
score: elem.score,
|
score: elem.score,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const entries = computed(() => {
|
||||||
|
const len = size(tableData.value)
|
||||||
|
return `${len} / ${Math.max(len, props.length)}`
|
||||||
|
})
|
||||||
|
|
||||||
const onAddRow = () => {
|
const onAddRow = () => {
|
||||||
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.ZSET)
|
dialogStore.openAddFieldsDialog(props.name, props.db, props.keyPath, props.keyCode, types.ZSET)
|
||||||
}
|
}
|
||||||
|
@ -293,14 +305,16 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
<div class="content-wrapper flex-box-v">
|
<div class="content-wrapper flex-box-v">
|
||||||
<content-toolbar
|
<content-toolbar
|
||||||
:db="props.db"
|
:db="props.db"
|
||||||
:decode="props.decode"
|
|
||||||
:key-code="props.keyCode"
|
:key-code="props.keyCode"
|
||||||
:key-path="props.keyPath"
|
:key-path="props.keyPath"
|
||||||
:key-type="keyType"
|
:key-type="keyType"
|
||||||
|
:loading="props.loading"
|
||||||
:server="props.name"
|
:server="props.name"
|
||||||
:ttl="ttl"
|
:ttl="ttl"
|
||||||
:view-as="props.viewAs"
|
class="value-item-part"
|
||||||
class="value-item-part" />
|
@delete="emit('delete')"
|
||||||
|
@reload="emit('reload')"
|
||||||
|
@rename="emit('rename')" />
|
||||||
<div class="tb2 value-item-part flex-box-h">
|
<div class="tb2 value-item-part flex-box-h">
|
||||||
<div class="flex-box-h">
|
<div class="flex-box-h">
|
||||||
<n-input-group>
|
<n-input-group>
|
||||||
|
@ -324,6 +338,22 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
<n-button-group>
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadList"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_more_entries"
|
||||||
|
@click="emit('loadmore')" />
|
||||||
|
<icon-button
|
||||||
|
:disabled="props.end || props.loading"
|
||||||
|
:icon="LoadAll"
|
||||||
|
border
|
||||||
|
size="18"
|
||||||
|
t-tooltip="interface.load_all_entries"
|
||||||
|
@click="emit('loadall')" />
|
||||||
|
</n-button-group>
|
||||||
<n-button :focusable="false" plain @click="onAddRow">
|
<n-button :focusable="false" plain @click="onAddRow">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="AddLink" size="18" />
|
<n-icon :component="AddLink" size="18" />
|
||||||
|
@ -331,24 +361,25 @@ const onUpdateFilter = (filters, sourceColumn) => {
|
||||||
{{ $t('interface.add_row') }}
|
{{ $t('interface.add_row') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="value-wrapper value-item-part fill-height flex-box-h">
|
<div class="value-wrapper value-item-part flex-box-v flex-item-expand">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:key="(row) => row.no"
|
:key="(row) => row.no"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:bottom-bordered="false"
|
:bottom-bordered="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="tableData"
|
:data="tableData"
|
||||||
|
:loading="props.loading"
|
||||||
:single-column="true"
|
:single-column="true"
|
||||||
:single-line="false"
|
:single-line="false"
|
||||||
|
class="flex-item-expand"
|
||||||
flex-height
|
flex-height
|
||||||
max-height="100%"
|
|
||||||
size="small"
|
size="small"
|
||||||
striped
|
striped
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
@update:filters="onUpdateFilter" />
|
@update:filters="onUpdateFilter" />
|
||||||
</div>
|
</div>
|
||||||
<div class="value-footer flex-box-h">
|
<div class="value-footer flex-box-h">
|
||||||
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ props.length }}</n-text>
|
<n-text v-if="!isNaN(props.length)">{{ $t('interface.entries') }}: {{ entries }}</n-text>
|
||||||
<n-divider v-if="!isNaN(props.length)" vertical />
|
<n-divider v-if="!isNaN(props.length)" vertical />
|
||||||
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
<n-text v-if="!isNaN(props.size)">{{ $t('interface.memory_usage') }}: {{ bytes(props.size) }}</n-text>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import AddZSetValue from '@/components/new_value/AddZSetValue.vue'
|
||||||
import NewStreamValue from '@/components/new_value/NewStreamValue.vue'
|
import NewStreamValue from '@/components/new_value/NewStreamValue.vue'
|
||||||
import { isEmpty, size, slice } from 'lodash'
|
import { isEmpty, size, slice } from 'lodash'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import useTabStore from 'stores/tab.js'
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const newForm = reactive({
|
const newForm = reactive({
|
||||||
|
@ -79,6 +80,7 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
const browserStore = useBrowserStore()
|
const browserStore = useBrowserStore()
|
||||||
|
const tab = useTabStore()
|
||||||
const onAdd = async () => {
|
const onAdd = async () => {
|
||||||
try {
|
try {
|
||||||
const { server, db, key, keyCode, type } = newForm
|
const { server, db, key, keyCode, type } = newForm
|
||||||
|
@ -87,6 +89,7 @@ const onAdd = async () => {
|
||||||
value = defaultValue[type]
|
value = defaultValue[type]
|
||||||
}
|
}
|
||||||
const keyName = isEmpty(keyCode) ? key : keyCode
|
const keyName = isEmpty(keyCode) ? key : keyCode
|
||||||
|
let updated = false
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case types.LIST:
|
case types.LIST:
|
||||||
{
|
{
|
||||||
|
@ -98,9 +101,7 @@ const onAdd = async () => {
|
||||||
}
|
}
|
||||||
const { success, msg } = data
|
const { success, msg } = data
|
||||||
if (success) {
|
if (success) {
|
||||||
if (newForm.reload) {
|
updated = true
|
||||||
browserStore.loadKeyValue(server, db, keyName).then(() => {})
|
|
||||||
}
|
|
||||||
$message.success(i18n.t('dialogue.handle_succ'))
|
$message.success(i18n.t('dialogue.handle_succ'))
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
|
@ -112,9 +113,7 @@ const onAdd = async () => {
|
||||||
{
|
{
|
||||||
const { success, msg } = await browserStore.addHashField(server, db, keyName, newForm.opType, value)
|
const { success, msg } = await browserStore.addHashField(server, db, keyName, newForm.opType, value)
|
||||||
if (success) {
|
if (success) {
|
||||||
if (newForm.reload) {
|
updated = true
|
||||||
browserStore.loadKeyValue(server, db, keyName).then(() => {})
|
|
||||||
}
|
|
||||||
$message.success(i18n.t('dialogue.handle_succ'))
|
$message.success(i18n.t('dialogue.handle_succ'))
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
|
@ -126,9 +125,7 @@ const onAdd = async () => {
|
||||||
{
|
{
|
||||||
const { success, msg } = await browserStore.addSetItem(server, db, keyName, value)
|
const { success, msg } = await browserStore.addSetItem(server, db, keyName, value)
|
||||||
if (success) {
|
if (success) {
|
||||||
if (newForm.reload) {
|
updated = true
|
||||||
browserStore.loadKeyValue(server, db, keyName).then(() => {})
|
|
||||||
}
|
|
||||||
$message.success(i18n.t('dialogue.handle_succ'))
|
$message.success(i18n.t('dialogue.handle_succ'))
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
|
@ -140,9 +137,7 @@ const onAdd = async () => {
|
||||||
{
|
{
|
||||||
const { success, msg } = await browserStore.addZSetItem(server, db, keyName, newForm.opType, value)
|
const { success, msg } = await browserStore.addZSetItem(server, db, keyName, newForm.opType, value)
|
||||||
if (success) {
|
if (success) {
|
||||||
if (newForm.reload) {
|
updated = true
|
||||||
browserStore.loadKeyValue(server, db, keyName).then(() => {})
|
|
||||||
}
|
|
||||||
$message.success(i18n.t('dialogue.handle_succ'))
|
$message.success(i18n.t('dialogue.handle_succ'))
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
|
@ -161,9 +156,7 @@ const onAdd = async () => {
|
||||||
slice(value, 1),
|
slice(value, 1),
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
if (newForm.reload) {
|
updated = true
|
||||||
browserStore.loadKeyValue(server, db, keyName).then(() => {})
|
|
||||||
}
|
|
||||||
$message.success(i18n.t('dialogue.handle_succ'))
|
$message.success(i18n.t('dialogue.handle_succ'))
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
|
@ -172,6 +165,12 @@ const onAdd = async () => {
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
if (newForm.reload) {
|
||||||
|
browserStore.reloadKey({ server, db, key: keyName })
|
||||||
|
}
|
||||||
|
}
|
||||||
dialogStore.closeAddFieldsDialog()
|
dialogStore.closeAddFieldsDialog()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
$message.error(e.message)
|
$message.error(e.message)
|
||||||
|
|
|
@ -130,7 +130,7 @@ const onAdd = async () => {
|
||||||
if (success) {
|
if (success) {
|
||||||
// select current key
|
// select current key
|
||||||
tabStore.setSelectedKeys(server, nodeKey)
|
tabStore.setSelectedKeys(server, nodeKey)
|
||||||
browserStore.loadKeyValue(server, db, key).then(() => {})
|
browserStore.loadKeySummary({ server, db, key })
|
||||||
} else if (!isEmpty(msg)) {
|
} else if (!isEmpty(msg)) {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { reactive, watch } from 'vue'
|
||||||
import useDialog from 'stores/dialog'
|
import useDialog from 'stores/dialog'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
|
import useTabStore from 'stores/tab.js'
|
||||||
|
|
||||||
const renameForm = reactive({
|
const renameForm = reactive({
|
||||||
server: '',
|
server: '',
|
||||||
|
@ -13,6 +14,7 @@ const renameForm = reactive({
|
||||||
|
|
||||||
const dialogStore = useDialog()
|
const dialogStore = useDialog()
|
||||||
const browserStore = useBrowserStore()
|
const browserStore = useBrowserStore()
|
||||||
|
const tab = useTabStore()
|
||||||
watch(
|
watch(
|
||||||
() => dialogStore.renameDialogVisible,
|
() => dialogStore.renameDialogVisible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
|
@ -30,9 +32,10 @@ const i18n = useI18n()
|
||||||
const onRename = async () => {
|
const onRename = async () => {
|
||||||
try {
|
try {
|
||||||
const { server, db, key, newKey } = renameForm
|
const { server, db, key, newKey } = renameForm
|
||||||
const { success, msg } = await browserStore.renameKey(server, db, key, newKey)
|
const { success, msg, nodeKey } = await browserStore.renameKey(server, db, key, newKey)
|
||||||
if (success) {
|
if (success) {
|
||||||
await browserStore.loadKeyValue(server, db, newKey)
|
tab.setSelectedKeys(server, nodeKey)
|
||||||
|
browserStore.loadKeySummary({ server, db, key: newKey })
|
||||||
$message.success(i18n.t('dialogue.handle_succ'))
|
$message.success(i18n.t('dialogue.handle_succ'))
|
||||||
} else {
|
} else {
|
||||||
$message.error(msg)
|
$message.error(msg)
|
||||||
|
|
|
@ -276,7 +276,11 @@ const handleSelectContextMenu = (key) => {
|
||||||
// browserStore.loadKeys(props.server, db, redisKey)
|
// browserStore.loadKeys(props.server, db, redisKey)
|
||||||
// break
|
// break
|
||||||
case 'value_reload':
|
case 'value_reload':
|
||||||
browserStore.loadKeyValue(props.server, db, redisKey)
|
browserStore.reloadKey({
|
||||||
|
server: props.server,
|
||||||
|
db,
|
||||||
|
key: redisKey,
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case 'key_remove':
|
case 'key_remove':
|
||||||
dialogStore.openDeleteKeyDialog(props.server, db, isEmpty(redisKey) ? '*' : redisKey + ':*')
|
dialogStore.openDeleteKeyDialog(props.server, db, isEmpty(redisKey) ? '*' : redisKey + ':*')
|
||||||
|
@ -378,14 +382,18 @@ const onUpdateSelectedKeys = (keys, options) => {
|
||||||
const { key, db } = node
|
const { key, db } = node
|
||||||
const redisKey = node.redisKeyCode || node.redisKey
|
const redisKey = node.redisKeyCode || node.redisKey
|
||||||
if (!includes(selectedKeys.value, key)) {
|
if (!includes(selectedKeys.value, key)) {
|
||||||
browserStore.loadKeyValue(props.server, db, redisKey)
|
browserStore.loadKeySummary({
|
||||||
|
server: props.server,
|
||||||
|
db,
|
||||||
|
key: redisKey,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// default is load blank key to display server status
|
// default is load blank key to display server status
|
||||||
browserStore.loadKeyValue(props.server, 0)
|
tabStore.openBlank(props.server)
|
||||||
} finally {
|
} finally {
|
||||||
tabStore.setSelectedKeys(props.server, keys)
|
tabStore.setSelectedKeys(props.server, keys)
|
||||||
}
|
}
|
||||||
|
@ -640,7 +648,7 @@ const nodeProps = ({ option }) => {
|
||||||
contextMenuParam.x = e.clientX
|
contextMenuParam.x = e.clientX
|
||||||
contextMenuParam.y = e.clientY
|
contextMenuParam.y = e.clientY
|
||||||
contextMenuParam.show = true
|
contextMenuParam.show = true
|
||||||
tabStore.setSelectedKeys(props.server, option.key)
|
onUpdateSelectedKeys([option.key], [option])
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// onMouseover() {
|
// onMouseover() {
|
||||||
|
|
|
@ -90,6 +90,8 @@
|
||||||
"new_key": "Add Key",
|
"new_key": "Add Key",
|
||||||
"load_more": "Load More Keys",
|
"load_more": "Load More Keys",
|
||||||
"load_all": "Load All Left Keys",
|
"load_all": "Load All Left Keys",
|
||||||
|
"load_more_entries": "Load More",
|
||||||
|
"load_all_entries": "Load All",
|
||||||
"more_action": "More Action",
|
"more_action": "More Action",
|
||||||
"nonexist_tab_content": "Selected key does not exist or no key is selected. Please retry",
|
"nonexist_tab_content": "Selected key does not exist or no key is selected. Please retry",
|
||||||
"empty_server_content": "Select and open a connection from the left",
|
"empty_server_content": "Select and open a connection from the left",
|
||||||
|
|
|
@ -90,6 +90,8 @@
|
||||||
"new_key": "添加新键",
|
"new_key": "添加新键",
|
||||||
"load_more": "加载更多键",
|
"load_more": "加载更多键",
|
||||||
"load_all": "加载剩余所有键",
|
"load_all": "加载剩余所有键",
|
||||||
|
"load_more_entries": "加载更多",
|
||||||
|
"load_all_entries": "加载全部",
|
||||||
"more_action": "更多操作",
|
"more_action": "更多操作",
|
||||||
"nonexist_tab_content": "所选键不存在或未选中任何键,请尝试刷新重试",
|
"nonexist_tab_content": "所选键不存在或未选中任何键,请尝试刷新重试",
|
||||||
"empty_server_content": "可以从左边选择并打开连接",
|
"empty_server_content": "可以从左边选择并打开连接",
|
||||||
|
|
|
@ -24,7 +24,8 @@ import {
|
||||||
DeleteKey,
|
DeleteKey,
|
||||||
FlushDB,
|
FlushDB,
|
||||||
GetCmdHistory,
|
GetCmdHistory,
|
||||||
GetKeyValue,
|
GetKeyDetail,
|
||||||
|
GetKeySummary,
|
||||||
GetSlowLogs,
|
GetSlowLogs,
|
||||||
LoadAllKeys,
|
LoadAllKeys,
|
||||||
LoadNextKeys,
|
LoadNextKeys,
|
||||||
|
@ -58,7 +59,7 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @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[]} [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 {number} [maxKeys] - max key count for database
|
* @property {number} [maxKeys] - max key count for database
|
||||||
* @property {boolean} [isLeaf]
|
* @property {boolean} [isLeaf]
|
||||||
|
@ -344,20 +345,23 @@ const useBrowserStore = defineStore('browser', {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load redis key
|
* load key summary info
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
* @param {number} db
|
* @param {number} db
|
||||||
* @param {string|number[]} [key] when key is null or blank, update tab to display normal content (blank content or server status)
|
* @param {string|number[]} [key] null or blank indicate that update tab to display normal content (blank content or server status)
|
||||||
* @param {string} [viewType]
|
* @return {Promise<void>}
|
||||||
* @param {string} [decodeType]
|
|
||||||
*/
|
*/
|
||||||
async loadKeyValue(server, db, key, viewType, decodeType) {
|
async loadKeySummary({ server, db, key }) {
|
||||||
try {
|
try {
|
||||||
const tab = useTabStore()
|
const tab = useTabStore()
|
||||||
if (!isEmpty(key)) {
|
if (!isEmpty(key)) {
|
||||||
const { data, success, msg } = await GetKeyValue(server, db, key, viewType, decodeType)
|
const { data, success, msg } = await GetKeySummary({
|
||||||
|
server,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
})
|
||||||
if (success) {
|
if (success) {
|
||||||
const { type, ttl, value, size, length, viewAs, decode } = data
|
const { type, ttl, size, length } = data
|
||||||
const k = decodeRedisKey(key)
|
const k = decodeRedisKey(key)
|
||||||
const binaryKey = k !== key
|
const binaryKey = k !== key
|
||||||
tab.upsertTab({
|
tab.upsertTab({
|
||||||
|
@ -368,16 +372,13 @@ const useBrowserStore = defineStore('browser', {
|
||||||
ttl,
|
ttl,
|
||||||
keyCode: binaryKey ? key : undefined,
|
keyCode: binaryKey ? key : undefined,
|
||||||
key: k,
|
key: k,
|
||||||
value,
|
|
||||||
size,
|
size,
|
||||||
length,
|
length,
|
||||||
viewAs,
|
|
||||||
decode,
|
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if (!isEmpty(msg)) {
|
if (!isEmpty(msg)) {
|
||||||
$message.error('load key fail: ' + msg)
|
$message.error('load key summary fail: ' + msg)
|
||||||
}
|
}
|
||||||
// its danger to delete "non-exists" key, just remove from tree view
|
// its danger to delete "non-exists" key, just remove from tree view
|
||||||
await this.deleteKey(server, db, key, true)
|
await this.deleteKey(server, db, key, true)
|
||||||
|
@ -397,10 +398,75 @@ const useBrowserStore = defineStore('browser', {
|
||||||
size: 0,
|
size: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
|
$message.error('')
|
||||||
} finally {
|
} finally {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reload key
|
||||||
|
* @param server
|
||||||
|
* @param db
|
||||||
|
* @param key
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
async reloadKey({ server, db, key }) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
try {
|
||||||
|
tab.updateLoading({ server, db, loading: true })
|
||||||
|
await this.loadKeySummary({ server, db, key })
|
||||||
|
await this.loadKeyDetail({ server, db, key, reset: true })
|
||||||
|
} finally {
|
||||||
|
tab.updateLoading({ server, db, loading: false })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load key content
|
||||||
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string|number[]} key
|
||||||
|
* @param {string} [viewType]
|
||||||
|
* @param {string} [decodeType]
|
||||||
|
* @param {string} [matchPattern]
|
||||||
|
* @param {boolean} [reset]
|
||||||
|
* @param {boolean} [full]
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
async loadKeyDetail({ server, db, key, viewType, decodeType, matchPattern, reset, full }) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
try {
|
||||||
|
tab.updateLoading({ server, db, loading: true })
|
||||||
|
const { data, success, msg } = await GetKeyDetail({
|
||||||
|
server,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
viewAs: viewType,
|
||||||
|
decodeType,
|
||||||
|
matchPattern,
|
||||||
|
full: full === true,
|
||||||
|
reset,
|
||||||
|
lite: true,
|
||||||
|
})
|
||||||
|
if (success) {
|
||||||
|
const { value, viewAs, decodeType: decode, end } = data
|
||||||
|
tab.updateValue({
|
||||||
|
server,
|
||||||
|
db,
|
||||||
|
key: decodeRedisKey(key),
|
||||||
|
value,
|
||||||
|
viewAs,
|
||||||
|
decode,
|
||||||
|
reset: reset || full === true,
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
tab.updateLoading({ server, db, loading: false })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* scan keys with prefix
|
* scan keys with prefix
|
||||||
* @param {string} connName
|
* @param {string} connName
|
||||||
|
@ -857,7 +923,14 @@ const useBrowserStore = defineStore('browser', {
|
||||||
try {
|
try {
|
||||||
const { data, success, msg } = await SetHashValue(connName, db, key, field, newField || '', value || '')
|
const { data, success, msg } = await SetHashValue(connName, db, key, field, newField || '', value || '')
|
||||||
if (success) {
|
if (success) {
|
||||||
const { updated = {} } = data
|
const { updated = {}, removed = [], replaced = {} } = data
|
||||||
|
const tab = useTabStore()
|
||||||
|
if (!isEmpty(removed)) {
|
||||||
|
tab.removeValueEntries({ server: connName, db, key, type: 'hash', entries: removed })
|
||||||
|
}
|
||||||
|
if (!isEmpty(updated)) {
|
||||||
|
tab.upsertValueEntries({ server: connName, db, key, type: 'hash', entries: updated })
|
||||||
|
}
|
||||||
return { success, updated }
|
return { success, updated }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -881,6 +954,8 @@ const useBrowserStore = defineStore('browser', {
|
||||||
const { data, success, msg } = await AddHashField(connName, db, key, action, fieldItems)
|
const { data, success, msg } = await AddHashField(connName, db, key, action, fieldItems)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { updated = {} } = data
|
const { updated = {} } = data
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({ server: connName, db, key, type: 'hash', entries: updated })
|
||||||
return { success, updated }
|
return { success, updated }
|
||||||
} else {
|
} else {
|
||||||
return { success: false, msg }
|
return { success: false, msg }
|
||||||
|
@ -903,6 +978,10 @@ const useBrowserStore = defineStore('browser', {
|
||||||
const { data, success, msg } = await SetHashValue(connName, db, key, field, '', '')
|
const { data, success, msg } = await SetHashValue(connName, db, key, field, '', '')
|
||||||
if (success) {
|
if (success) {
|
||||||
const { removed = [] } = data
|
const { removed = [] } = data
|
||||||
|
if (!isEmpty(removed)) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.removeValueEntries({ server: connName, db, key, type: 'hash', entries: removed })
|
||||||
|
}
|
||||||
return { success, removed }
|
return { success, removed }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -935,13 +1014,24 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @param db
|
* @param db
|
||||||
* @param key
|
* @param key
|
||||||
* @param values
|
* @param values
|
||||||
* @returns {Promise<[msg]: string, success: boolean, [item]: []>}
|
* @returns {Promise<{[msg]: string, success: boolean, [item]: []}>}
|
||||||
*/
|
*/
|
||||||
async prependListItem(connName, db, key, values) {
|
async prependListItem(connName, db, key, values) {
|
||||||
try {
|
try {
|
||||||
const { data, success, msg } = await AddListItem(connName, db, key, 0, values)
|
const { data, success, msg } = await AddListItem(connName, db, key, 0, values)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { left = [] } = data
|
const { left = [] } = data
|
||||||
|
if (!isEmpty(left)) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({
|
||||||
|
server: connName,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
type: 'list',
|
||||||
|
entries: right,
|
||||||
|
prepend: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
return { success, item: left }
|
return { success, item: left }
|
||||||
} else {
|
} else {
|
||||||
return { success: false, msg }
|
return { success: false, msg }
|
||||||
|
@ -957,13 +1047,24 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @param db
|
* @param db
|
||||||
* @param key
|
* @param key
|
||||||
* @param values
|
* @param values
|
||||||
* @returns {Promise<[msg]: string, success: boolean, [item]: any[]>}
|
* @returns {Promise<{[msg]: string, success: boolean, [item]: any[]}>}
|
||||||
*/
|
*/
|
||||||
async appendListItem(connName, db, key, values) {
|
async appendListItem(connName, db, key, values) {
|
||||||
try {
|
try {
|
||||||
const { data, success, msg } = await AddListItem(connName, db, key, 1, values)
|
const { data, success, msg } = await AddListItem(connName, db, key, 1, values)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { right = [] } = data
|
const { right = [] } = data
|
||||||
|
if (!isEmpty(right)) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({
|
||||||
|
server: connName,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
type: 'list',
|
||||||
|
entries: right,
|
||||||
|
prepend: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
return { success, item: right }
|
return { success, item: right }
|
||||||
} else {
|
} else {
|
||||||
return { success: false, msg }
|
return { success: false, msg }
|
||||||
|
@ -987,6 +1088,16 @@ const useBrowserStore = defineStore('browser', {
|
||||||
const { data, success, msg } = await SetListItem(connName, db, key, index, value)
|
const { data, success, msg } = await SetListItem(connName, db, key, index, value)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { updated = {} } = data
|
const { updated = {} } = data
|
||||||
|
if (!isEmpty(updated)) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({
|
||||||
|
server: connName,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
type: 'list',
|
||||||
|
entries: updated,
|
||||||
|
})
|
||||||
|
}
|
||||||
return { success, updated }
|
return { success, updated }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -1009,6 +1120,16 @@ const useBrowserStore = defineStore('browser', {
|
||||||
const { data, success, msg } = await SetListItem(connName, db, key, index, '')
|
const { data, success, msg } = await SetListItem(connName, db, key, index, '')
|
||||||
if (success) {
|
if (success) {
|
||||||
const { removed = [] } = data
|
const { removed = [] } = data
|
||||||
|
if (!isEmpty(removed)) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.removeValueEntries({
|
||||||
|
server: connName,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
type: 'list',
|
||||||
|
entries: removed,
|
||||||
|
})
|
||||||
|
}
|
||||||
return { success, removed }
|
return { success, removed }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -1023,13 +1144,18 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @param {string} connName
|
* @param {string} connName
|
||||||
* @param {number} db
|
* @param {number} db
|
||||||
* @param {string|number} key
|
* @param {string|number} key
|
||||||
* @param {string} value
|
* @param {string|string[]} value
|
||||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
||||||
*/
|
*/
|
||||||
async addSetItem(connName, db, key, value) {
|
async addSetItem(connName, db, key, value) {
|
||||||
try {
|
try {
|
||||||
const { success, msg } = await SetSetItem(connName, db, key, false, [value])
|
if (!value instanceof Array) {
|
||||||
|
value = [value]
|
||||||
|
}
|
||||||
|
const { data, success, msg } = await SetSetItem(connName, db, key, false, value)
|
||||||
if (success) {
|
if (success) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({ server: connName, db, key, type: 'set', entries: value })
|
||||||
return { success }
|
return { success }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -1052,6 +1178,8 @@ const useBrowserStore = defineStore('browser', {
|
||||||
try {
|
try {
|
||||||
const { success, msg } = await UpdateSetItem(connName, db, key, value, newValue)
|
const { success, msg } = await UpdateSetItem(connName, db, key, value, newValue)
|
||||||
if (success) {
|
if (success) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({ server: connName, db, key, type: 'set', entries: { [value]: newValue } })
|
||||||
return { success: true }
|
return { success: true }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -1073,6 +1201,8 @@ const useBrowserStore = defineStore('browser', {
|
||||||
try {
|
try {
|
||||||
const { success, msg } = await SetSetItem(connName, db, key, true, [value])
|
const { success, msg } = await SetSetItem(connName, db, key, true, [value])
|
||||||
if (success) {
|
if (success) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.removeValueEntries({ server: connName, db, key, type: 'set', entries: [value] })
|
||||||
return { success }
|
return { success }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -1094,6 +1224,8 @@ const useBrowserStore = defineStore('browser', {
|
||||||
async addZSetItem(connName, db, key, action, vs) {
|
async addZSetItem(connName, db, key, action, vs) {
|
||||||
try {
|
try {
|
||||||
const { success, msg } = await AddZSetValue(connName, db, key, action, vs)
|
const { success, msg } = await AddZSetValue(connName, db, key, action, vs)
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({ server: connName, db, key, type: 'zset', entries: vs })
|
||||||
if (success) {
|
if (success) {
|
||||||
return { success }
|
return { success }
|
||||||
} else {
|
} else {
|
||||||
|
@ -1119,6 +1251,13 @@ const useBrowserStore = defineStore('browser', {
|
||||||
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, newValue, score)
|
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, newValue, score)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { updated, removed } = data
|
const { updated, removed } = data
|
||||||
|
const tab = useTabStore()
|
||||||
|
if (!isEmpty(updated)) {
|
||||||
|
tab.upsertValueEntries({ server: connName, db, key, type: 'zset', entries: updated })
|
||||||
|
}
|
||||||
|
if (!isEmpty(removed)) {
|
||||||
|
tab.removeValueEntries({ server: connName, db, key, type: 'zset', entries: removed })
|
||||||
|
}
|
||||||
return { success, updated, removed }
|
return { success, updated, removed }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
|
@ -1141,7 +1280,11 @@ const useBrowserStore = defineStore('browser', {
|
||||||
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, '', 0)
|
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, '', 0)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { removed } = data
|
const { removed } = data
|
||||||
return { success, removed }
|
if (!isEmpty(removed)) {
|
||||||
|
const tab = useTabStore()
|
||||||
|
tab.removeValueEntries({ server: connName, db, key, type: 'zset', entries: removed })
|
||||||
|
}
|
||||||
|
return { success }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
}
|
}
|
||||||
|
@ -1157,14 +1300,22 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @param {string|number[]} 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}>}
|
||||||
*/
|
*/
|
||||||
async addStreamValue(connName, db, key, id, values) {
|
async addStreamValue(connName, db, key, id, values) {
|
||||||
try {
|
try {
|
||||||
const { data = {}, success, msg } = await AddStreamValue(connName, db, key, id, values)
|
const { data = {}, success, msg } = await AddStreamValue(connName, db, key, id, values)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { updated = {} } = data
|
const { updateID } = data
|
||||||
return { success, updated }
|
const tab = useTabStore()
|
||||||
|
tab.upsertValueEntries({
|
||||||
|
server: connName,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
type: 'stream',
|
||||||
|
entries: [{ id: updateID, value: values }],
|
||||||
|
})
|
||||||
|
return { success }
|
||||||
} else {
|
} else {
|
||||||
return { success: false, msg }
|
return { success: false, msg }
|
||||||
}
|
}
|
||||||
|
@ -1179,7 +1330,7 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @param {number} db
|
* @param {number} db
|
||||||
* @param {string|number[]} 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}>}
|
||||||
*/
|
*/
|
||||||
async removeStreamValues(connName, db, key, ids) {
|
async removeStreamValues(connName, db, key, ids) {
|
||||||
if (typeof ids === 'string') {
|
if (typeof ids === 'string') {
|
||||||
|
@ -1188,8 +1339,9 @@ const useBrowserStore = defineStore('browser', {
|
||||||
try {
|
try {
|
||||||
const { data = {}, success, msg } = await RemoveStreamValues(connName, db, key, ids)
|
const { data = {}, success, msg } = await RemoveStreamValues(connName, db, key, ids)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { removed = [] } = data
|
const tab = useTabStore()
|
||||||
return { success, removed }
|
tab.removeValueEntries({ server: connName, db, key, type: 'stream', entries: ids })
|
||||||
|
return { success }
|
||||||
} else {
|
} else {
|
||||||
return { success, msg }
|
return { success, msg }
|
||||||
}
|
}
|
||||||
|
@ -1424,7 +1576,7 @@ const useBrowserStore = defineStore('browser', {
|
||||||
* @param {number} db
|
* @param {number} db
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {string} newKey
|
* @param {string} newKey
|
||||||
* @returns {Promise<{[msg]: string, success: boolean}>}
|
* @returns {Promise<{[msg]: string, success: boolean, [nodeKey]: string}>}
|
||||||
*/
|
*/
|
||||||
async renameKey(connName, db, key, newKey) {
|
async renameKey(connName, db, key, newKey) {
|
||||||
const { success = false, msg } = await RenameKey(connName, db, key, newKey)
|
const { success = false, msg } = await RenameKey(connName, db, key, newKey)
|
||||||
|
@ -1432,7 +1584,7 @@ const useBrowserStore = defineStore('browser', {
|
||||||
// delete old key and add new key struct
|
// delete old key and add new key struct
|
||||||
this._deleteKeyNode(connName, db, key)
|
this._deleteKeyNode(connName, db, key)
|
||||||
this._addKeyNodes(connName, db, [newKey])
|
this._addKeyNodes(connName, db, [newKey])
|
||||||
return { success: true }
|
return { success: true, nodeKey: `${connName}/db${db}#${ConnectionType.RedisValue}/${newKey}` }
|
||||||
} else {
|
} else {
|
||||||
return { success: false, msg }
|
return { success: false, msg }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { find, findIndex, get, isEmpty, set, size } from 'lodash'
|
import { assign, find, findIndex, get, indexOf, isEmpty, pullAt, remove, set, size } from 'lodash'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
const useTabStore = defineStore('tab', {
|
const useTabStore = defineStore('tab', {
|
||||||
|
@ -11,12 +11,18 @@ const useTabStore = defineStore('tab', {
|
||||||
* @property {string} [icon] tab icon
|
* @property {string} [icon] tab icon
|
||||||
* @property {string[]} selectedKeys
|
* @property {string[]} selectedKeys
|
||||||
* @property {string} [type] key type
|
* @property {string} [type] key type
|
||||||
* @property {Object|Array} [value] key value
|
* @property {*} [value] key value
|
||||||
* @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 {number[]|null|undefined} [keyCode] current key name as char array
|
||||||
|
* @param {number} [size] memory usage
|
||||||
|
* @param {number} [length] length of content or entries
|
||||||
* @property {int} [ttl] ttl of current key
|
* @property {int} [ttl] ttl of current key
|
||||||
|
* @param {string} [viewAs]
|
||||||
|
* @param {string} [decode]
|
||||||
|
* @param {boolean} [end]
|
||||||
|
* @param {boolean} [loading]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,6 +88,10 @@ const useTabStore = defineStore('tab', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openBlank(server) {
|
||||||
|
this.upsertTab({ server, db: 0 })
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update or insert a new tab if not exists with the same name
|
* update or insert a new tab if not exists with the same name
|
||||||
* @param {string} subTab
|
* @param {string} subTab
|
||||||
|
@ -94,10 +104,8 @@ const useTabStore = defineStore('tab', {
|
||||||
* @param {number} [size]
|
* @param {number} [size]
|
||||||
* @param {number} [length]
|
* @param {number} [length]
|
||||||
* @param {*} [value]
|
* @param {*} [value]
|
||||||
* @param {string} [viewAs]
|
|
||||||
* @param {string} [decode]
|
|
||||||
*/
|
*/
|
||||||
upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, length, value, viewAs, decode }) {
|
upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, length }) {
|
||||||
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({
|
||||||
|
@ -112,9 +120,7 @@ const useTabStore = defineStore('tab', {
|
||||||
keyCode,
|
keyCode,
|
||||||
size,
|
size,
|
||||||
length,
|
length,
|
||||||
value,
|
value: undefined,
|
||||||
viewAs,
|
|
||||||
decode,
|
|
||||||
})
|
})
|
||||||
tabIndex = this.tabList.length - 1
|
tabIndex = this.tabList.length - 1
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,16 +137,239 @@ const useTabStore = defineStore('tab', {
|
||||||
tab.keyCode = keyCode
|
tab.keyCode = keyCode
|
||||||
tab.size = size
|
tab.size = size
|
||||||
tab.length = length
|
tab.length = length
|
||||||
tab.value = value
|
tab.value = undefined
|
||||||
tab.viewAs = viewAs
|
|
||||||
tab.decode = decode
|
|
||||||
}
|
}
|
||||||
this._setActivatedIndex(tabIndex, true, subTab)
|
this._setActivatedIndex(tabIndex, true, subTab)
|
||||||
// this.activatedTab = tab.name
|
// this.activatedTab = tab.name
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update ttl by tag
|
* keep update value in tab
|
||||||
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {*} value
|
||||||
|
* @param {string} [viewAs]
|
||||||
|
* @param {string] [decode]
|
||||||
|
* @param {boolean} reset
|
||||||
|
* @param {boolean} [end] keep end status if not set
|
||||||
|
*/
|
||||||
|
updateValue({ server, db, key, value, viewAs, decode, reset, end }) {
|
||||||
|
const tab = find(this.tabList, { name: server, db, key })
|
||||||
|
if (tab == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.viewAs = viewAs || tab.viewAs
|
||||||
|
tab.decode = decode || tab.decode
|
||||||
|
if (typeof end === 'boolean') {
|
||||||
|
tab.end = end
|
||||||
|
}
|
||||||
|
if (!reset && typeof value === 'object') {
|
||||||
|
if (value instanceof Array) {
|
||||||
|
tab.value = tab.value || []
|
||||||
|
tab.value.push(...value)
|
||||||
|
} else {
|
||||||
|
tab.value = assign(value, tab.value || {})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tab.value = value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update or insert value entries
|
||||||
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} type
|
||||||
|
* @param {string[]|Object.<string, number>|Object.<number, string>} entries
|
||||||
|
* @param {boolean} [prepend] for list only
|
||||||
|
* @param {boolean} [reset]
|
||||||
|
* @param {boolean} [nocheck] ignore conflict checking for hash/set/zset
|
||||||
|
*/
|
||||||
|
upsertValueEntries({ server, db, key, type, entries, prepend, reset, nocheck }) {
|
||||||
|
const tab = find(this.tabList, { name: server, db, key })
|
||||||
|
if (tab == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case 'list': // string[] | Object.<number, string>
|
||||||
|
if (entries instanceof Array) {
|
||||||
|
// append or prepend items
|
||||||
|
if (reset === true) {
|
||||||
|
tab.value = entries
|
||||||
|
} else {
|
||||||
|
tab.value = tab.value || []
|
||||||
|
if (prepend === true) {
|
||||||
|
tab.value = [...entries, ...tab.value]
|
||||||
|
} else {
|
||||||
|
tab.value.push(...entries)
|
||||||
|
}
|
||||||
|
tab.length += size(entries)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// replace by index
|
||||||
|
tab.value = tab.value || []
|
||||||
|
for (const idx in entries) {
|
||||||
|
set(tab.value, idx, entries[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'hash': // Object.<string, string>
|
||||||
|
if (reset === true) {
|
||||||
|
tab.value = {}
|
||||||
|
tab.length = 0
|
||||||
|
} else {
|
||||||
|
tab.value = tab.value || {}
|
||||||
|
}
|
||||||
|
for (const k in entries) {
|
||||||
|
if (nocheck !== true && !tab.value.hasOwnProperty(k)) {
|
||||||
|
tab.length += 1
|
||||||
|
}
|
||||||
|
tab.value[k] = entries[k]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'set': // string[] | Object.{string, string}
|
||||||
|
if (reset === true) {
|
||||||
|
tab.value = entries
|
||||||
|
} else {
|
||||||
|
tab.value = tab.value || []
|
||||||
|
if (entries instanceof Array) {
|
||||||
|
// add items
|
||||||
|
for (const elem of entries) {
|
||||||
|
if (nocheck !== true && indexOf(tab.value, elem) === -1) {
|
||||||
|
tab.value.push(elem)
|
||||||
|
tab.length += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// replace items
|
||||||
|
for (const k in entries) {
|
||||||
|
const idx = indexOf(tab.value, k)
|
||||||
|
if (idx !== -1) {
|
||||||
|
tab.value[idx] = entries[k]
|
||||||
|
} else {
|
||||||
|
tab.value.push(entries[k])
|
||||||
|
tab.length += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'zset': // {value: string, score: number}
|
||||||
|
if (reset === true) {
|
||||||
|
tab.value = Object.entries(entries).map(([value, score]) => ({ value, score }))
|
||||||
|
} else {
|
||||||
|
tab.value = tab.value || []
|
||||||
|
for (const val in entries) {
|
||||||
|
if (nocheck !== true) {
|
||||||
|
const ent = find(tab.value, (e) => e.value === val)
|
||||||
|
if (ent != null) {
|
||||||
|
ent.score = entries[val]
|
||||||
|
} else {
|
||||||
|
tab.value.push({ value: val, score: entries[val] })
|
||||||
|
tab.length += 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tab.value.push({ value: val, score: entries[val] })
|
||||||
|
tab.length += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'stream': // [{id: string, value: []any}]
|
||||||
|
if (reset === true) {
|
||||||
|
tab.value = entries
|
||||||
|
} else {
|
||||||
|
tab.value = tab.value || []
|
||||||
|
tab.value = [...entries, ...tab.value]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove value entries
|
||||||
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string} type
|
||||||
|
* @param {string[] | number[]} entries
|
||||||
|
*/
|
||||||
|
removeValueEntries({ server, db, key, type, entries }) {
|
||||||
|
const tab = find(this.tabList, { name: server, db, key })
|
||||||
|
if (tab == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case 'list': // string[] | number[]
|
||||||
|
tab.value = tab.value || []
|
||||||
|
if (typeof entries[0] === 'number') {
|
||||||
|
// remove by index、
|
||||||
|
entries.sort((a, b) => b - a)
|
||||||
|
const removed = pullAt(tab.value, ...entries)
|
||||||
|
tab.length -= size(removed)
|
||||||
|
} else {
|
||||||
|
// append or prepend items
|
||||||
|
for (const elem of entries) {
|
||||||
|
if (!isEmpty(remove(tab.value, elem))) {
|
||||||
|
tab.length -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'hash': // string[]
|
||||||
|
tab.value = tab.value || {}
|
||||||
|
for (const k of entries) {
|
||||||
|
if (tab.value.hasOwnProperty(k)) {
|
||||||
|
delete tab.value[k]
|
||||||
|
tab.length -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'set': // []string
|
||||||
|
tab.value = tab.value || []
|
||||||
|
tab.length -= size(remove(tab.value, (v) => entries.indexOf(v) >= 0))
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'zset': // string[]
|
||||||
|
tab.value = tab.value || []
|
||||||
|
tab.length -= size(remove(tab.value, (v) => entries.indexOf(v.value) >= 0))
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'stream': // string[]
|
||||||
|
tab.value = tab.value || []
|
||||||
|
tab.length -= size(remove(tab.value, (v) => entries.indexOf(v.id) >= 0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update loading status of content in tab
|
||||||
|
* @param {string} server
|
||||||
|
* @param {number} db
|
||||||
|
* @param {boolean} loading
|
||||||
|
*/
|
||||||
|
updateLoading({ server, db, loading }) {
|
||||||
|
const tab = find(this.tabList, { name: server, db })
|
||||||
|
if (tab == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.loading = loading
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update ttl in tab
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
* @param {number} db
|
* @param {number} db
|
||||||
* @param {string|number[]} key
|
* @param {string|number[]} key
|
||||||
|
|
|
@ -92,7 +92,8 @@ body {
|
||||||
.value-wrapper {
|
.value-wrapper {
|
||||||
//border-top: v-bind('themeVars.borderColor') 1px solid;
|
//border-top: v-bind('themeVars.borderColor') 1px solid;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
height: 100%;
|
//height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value-item-part {
|
.value-item-part {
|
||||||
|
@ -136,3 +137,7 @@ body {
|
||||||
.n-modal-mask {
|
.n-modal-mask {
|
||||||
--wails-draggable: drag;
|
--wails-draggable: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.n-tabs .n-tabs-nav {
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue