Compare commits

..

No commits in common. "bfb54070304798293e4b387ccda1dfd775a244ba" and "aafa0c5432acfd5b12d55be683ebd6729b9c17f4" have entirely different histories.

19 changed files with 349 additions and 412 deletions

View File

@ -251,8 +251,6 @@ func (b *browserService) getRedisClient(connName string, db int) (item connectio
err = fmt.Errorf("create conenction error: %s", err.Error()) err = fmt.Errorf("create conenction error: %s", err.Error())
return return
} }
_ = client.Do(b.ctx, "CLIENT", "SETNAME", url.QueryEscape(selConn.Name)).Err()
// add hook to each node in cluster mode // add hook to each node in cluster mode
var cluster *redis.ClusterClient var cluster *redis.ClusterClient
if cluster, ok = client.(*redis.ClusterClient); ok { if cluster, ok = client.(*redis.ClusterClient); ok {
@ -597,7 +595,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
// define get entry cursor function // define get entry cursor function
getEntryCursor := func() (uint64, string, bool) { getEntryCursor := func() (uint64, string) {
if entry, ok := entryCors[param.DB]; !ok || entry.Key != key || entry.Pattern != matchPattern { if entry, ok := entryCors[param.DB]; !ok || entry.Key != key || entry.Pattern != matchPattern {
// not the same key or match pattern, reset cursor // not the same key or match pattern, reset cursor
entry = entryCursor{ entry = entryCursor{
@ -607,9 +605,9 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
Cursor: 0, Cursor: 0,
} }
entryCors[param.DB] = entry entryCors[param.DB] = entry
return 0, "", true return 0, ""
} else { } else {
return entry.Cursor, entry.XLast, false return entry.Cursor, entry.XLast
} }
} }
// define set entry cursor function // define set entry cursor function
@ -641,21 +639,19 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
//data.Value, data.Decode, data.Format = strutil.ConvertTo(str, param.Decode, param.Format) //data.Value, data.Decode, data.Format = strutil.ConvertTo(str, param.Decode, param.Format)
case "list": case "list":
loadListHandle := func() ([]types.ListEntryItem, bool, bool, error) { loadListHandle := func() ([]types.ListEntryItem, bool, error) {
var loadVal []string var loadVal []string
var cursor uint64 var cursor uint64
var reset bool
var subErr error var subErr error
doFilter := matchPattern != "*" if param.Full {
if param.Full || doFilter {
// load all // load all
cursor, reset = 0, true cursor = 0
loadVal, subErr = client.LRange(ctx, key, 0, -1).Result() loadVal, subErr = client.LRange(ctx, key, 0, -1).Result()
} else { } else {
if param.Reset { if param.Reset {
cursor, reset = 0, true cursor = 0
} else { } else {
cursor, _, reset = getEntryCursor() cursor, _ = getEntryCursor()
} }
scanSize := int64(Preferences().GetScanSize()) scanSize := int64(Preferences().GetScanSize())
loadVal, subErr = client.LRange(ctx, key, int64(cursor), int64(cursor)+scanSize-1).Result() loadVal, subErr = client.LRange(ctx, key, int64(cursor), int64(cursor)+scanSize-1).Result()
@ -666,54 +662,42 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
setEntryCursor(cursor) setEntryCursor(cursor)
items := make([]types.ListEntryItem, 0, len(loadVal)) items := make([]types.ListEntryItem, len(loadVal))
for _, val := range loadVal { for i, val := range loadVal {
if doFilter && !strings.Contains(val, param.MatchPattern) { items[i].Value = val
continue
}
items = append(items, types.ListEntryItem{
Value: val,
})
if doConvert { if doConvert {
if dv, _, _ := strutil.ConvertTo(val, param.Decode, param.Format); dv != val { if dv, _, _ := strutil.ConvertTo(val, param.Decode, param.Format); dv != val {
items[len(items)-1].DisplayValue = dv items[i].DisplayValue = dv
} }
} }
} }
if subErr != nil { if subErr != nil {
return items, reset, false, subErr return items, false, subErr
} }
return items, reset, cursor == 0, nil return items, cursor == 0, nil
} }
data.Value, data.Reset, data.End, err = loadListHandle() data.Value, data.End, err = loadListHandle()
data.Match, data.Decode, data.Format = param.MatchPattern, param.Decode, param.Format data.Decode, data.Format = param.Decode, param.Format
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
case "hash": case "hash":
if !strings.HasPrefix(matchPattern, "*") { loadHashHandle := func() ([]types.HashEntryItem, bool, error) {
matchPattern = "*" + matchPattern
}
if !strings.HasSuffix(matchPattern, "*") {
matchPattern = matchPattern + "*"
}
loadHashHandle := func() ([]types.HashEntryItem, bool, bool, error) {
var items []types.HashEntryItem var items []types.HashEntryItem
var loadedVal []string var loadedVal []string
var cursor uint64 var cursor uint64
var reset bool
var subErr error var subErr error
scanSize := int64(Preferences().GetScanSize()) scanSize := int64(Preferences().GetScanSize())
if param.Full || matchPattern != "*" { if param.Full {
// load all // load all
cursor, reset = 0, true cursor = 0
for { for {
loadedVal, cursor, subErr = client.HScan(ctx, key, cursor, matchPattern, scanSize).Result() loadedVal, cursor, subErr = client.HScan(ctx, key, cursor, "*", scanSize).Result()
if subErr != nil { if subErr != nil {
return nil, reset, false, subErr return nil, false, subErr
} }
for i := 0; i < len(loadedVal); i += 2 { for i := 0; i < len(loadedVal); i += 2 {
items = append(items, types.HashEntryItem{ items = append(items, types.HashEntryItem{
@ -732,13 +716,13 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} else { } else {
if param.Reset { if param.Reset {
cursor, reset = 0, true cursor = 0
} else { } else {
cursor, _, reset = getEntryCursor() cursor, _ = getEntryCursor()
} }
loadedVal, cursor, subErr = client.HScan(ctx, key, cursor, matchPattern, scanSize).Result() loadedVal, cursor, subErr = client.HScan(ctx, key, cursor, matchPattern, scanSize).Result()
if subErr != nil { if subErr != nil {
return nil, reset, false, subErr return nil, false, subErr
} }
loadedLen := len(loadedVal) loadedLen := len(loadedVal)
items = make([]types.HashEntryItem, loadedLen/2) items = make([]types.HashEntryItem, loadedLen/2)
@ -753,37 +737,30 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} }
setEntryCursor(cursor) setEntryCursor(cursor)
return items, reset, cursor == 0, nil return items, cursor == 0, nil
} }
data.Value, data.Reset, data.End, err = loadHashHandle() data.Value, data.End, err = loadHashHandle()
data.Match, data.Decode, data.Format = param.MatchPattern, param.Decode, param.Format data.Decode, data.Format = param.Decode, param.Format
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
case "set": case "set":
if !strings.HasPrefix(matchPattern, "*") { loadSetHandle := func() ([]types.SetEntryItem, bool, error) {
matchPattern = "*" + matchPattern
}
if !strings.HasSuffix(matchPattern, "*") {
matchPattern = matchPattern + "*"
}
loadSetHandle := func() ([]types.SetEntryItem, bool, bool, error) {
var items []types.SetEntryItem var items []types.SetEntryItem
var cursor uint64 var cursor uint64
var reset bool
var subErr error var subErr error
var loadedKey []string var loadedKey []string
scanSize := int64(Preferences().GetScanSize()) scanSize := int64(Preferences().GetScanSize())
if param.Full || matchPattern != "*" { if param.Full {
// load all // load all
cursor, reset = 0, true cursor = 0
for { for {
loadedKey, cursor, subErr = client.SScan(ctx, key, cursor, matchPattern, scanSize).Result() loadedKey, cursor, subErr = client.SScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
if subErr != nil { if subErr != nil {
return items, reset, false, subErr return items, false, subErr
} }
for _, val := range loadedKey { for _, val := range loadedKey {
items = append(items, types.SetEntryItem{ items = append(items, types.SetEntryItem{
@ -801,11 +778,11 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} else { } else {
if param.Reset { if param.Reset {
cursor, reset = 0, true cursor = 0
} else { } else {
cursor, _, reset = getEntryCursor() cursor, _ = getEntryCursor()
} }
loadedKey, cursor, subErr = client.SScan(ctx, key, cursor, matchPattern, scanSize).Result() loadedKey, cursor, subErr = client.SScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
items = make([]types.SetEntryItem, len(loadedKey)) items = make([]types.SetEntryItem, len(loadedKey))
for i, val := range loadedKey { for i, val := range loadedKey {
items[i].Value = val items[i].Value = val
@ -817,36 +794,29 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} }
setEntryCursor(cursor) setEntryCursor(cursor)
return items, reset, cursor == 0, nil return items, cursor == 0, nil
} }
data.Value, data.Reset, data.End, err = loadSetHandle() data.Value, data.End, err = loadSetHandle()
data.Match, data.Decode, data.Format = param.MatchPattern, param.Decode, param.Format data.Decode, data.Format = param.Decode, param.Format
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
case "zset": case "zset":
if !strings.HasPrefix(matchPattern, "*") { loadZSetHandle := func() ([]types.ZSetEntryItem, bool, error) {
matchPattern = "*" + matchPattern
}
if !strings.HasSuffix(matchPattern, "*") {
matchPattern = matchPattern + "*"
}
loadZSetHandle := func() ([]types.ZSetEntryItem, bool, bool, error) {
var items []types.ZSetEntryItem var items []types.ZSetEntryItem
var reset bool
var cursor uint64 var cursor uint64
scanSize := int64(Preferences().GetScanSize()) scanSize := int64(Preferences().GetScanSize())
var loadedVal []string var loadedVal []string
if param.Full || matchPattern != "*" { if param.Full {
// load all // load all
cursor, reset = 0, true cursor = 0
for { for {
loadedVal, cursor, err = client.ZScan(ctx, key, cursor, matchPattern, scanSize).Result() loadedVal, cursor, err = client.ZScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
if err != nil { if err != nil {
return items, reset, false, err return items, false, err
} }
var score float64 var score float64
for i := 0; i < len(loadedVal); i += 2 { for i := 0; i < len(loadedVal); i += 2 {
@ -868,11 +838,11 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} else { } else {
if param.Reset { if param.Reset {
cursor, reset = 0, true cursor = 0
} else { } else {
cursor, _, reset = getEntryCursor() cursor, _ = getEntryCursor()
} }
loadedVal, cursor, err = client.ZScan(ctx, key, cursor, matchPattern, scanSize).Result() loadedVal, cursor, err = client.ZScan(ctx, key, cursor, param.MatchPattern, scanSize).Result()
loadedLen := len(loadedVal) loadedLen := len(loadedVal)
items = make([]types.ZSetEntryItem, loadedLen/2) items = make([]types.ZSetEntryItem, loadedLen/2)
var score float64 var score float64
@ -889,32 +859,30 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} }
setEntryCursor(cursor) setEntryCursor(cursor)
return items, reset, cursor == 0, nil return items, cursor == 0, nil
} }
data.Value, data.Reset, data.End, err = loadZSetHandle() data.Value, data.End, err = loadZSetHandle()
data.Match, data.Decode, data.Format = param.MatchPattern, param.Decode, param.Format data.Decode, data.Format = param.Decode, param.Format
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return
} }
case "stream": case "stream":
loadStreamHandle := func() ([]types.StreamEntryItem, bool, bool, error) { loadStreamHandle := func() ([]types.StreamEntryItem, bool, error) {
var msgs []redis.XMessage var msgs []redis.XMessage
var last string var last string
var reset bool if param.Full {
doFilter := matchPattern != "*"
if param.Full || doFilter {
// load all // load all
last, reset = "", true last = ""
msgs, err = client.XRevRange(ctx, key, "+", "-").Result() msgs, err = client.XRevRange(ctx, key, "+", "-").Result()
} else { } else {
scanSize := int64(Preferences().GetScanSize()) scanSize := int64(Preferences().GetScanSize())
if param.Reset { if param.Reset {
last = "" last = ""
} else { } else {
_, last, reset = getEntryCursor() _, last = getEntryCursor()
} }
if len(last) <= 0 { if len(last) <= 0 {
last = "+" last = "+"
@ -934,30 +902,24 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} }
setEntryXLast(last) setEntryXLast(last)
items := make([]types.StreamEntryItem, 0, len(msgs)) items := make([]types.StreamEntryItem, len(msgs))
for _, msg := range msgs { for i, msg := range msgs {
it := types.StreamEntryItem{ items[i].ID = msg.ID
ID: msg.ID, items[i].Value = msg.Values
Value: msg.Values,
}
if vb, merr := json.Marshal(msg.Values); merr != nil { if vb, merr := json.Marshal(msg.Values); merr != nil {
it.DisplayValue = "{}" items[i].DisplayValue = "{}"
} else { } else {
it.DisplayValue, _, _ = strutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON) items[i].DisplayValue, _, _ = strutil.ConvertTo(string(vb), types.DECODE_NONE, types.FORMAT_JSON)
} }
if doFilter && !strings.Contains(it.DisplayValue, param.MatchPattern) {
continue
}
items = append(items, it)
} }
if err != nil { if err != nil {
return items, reset, false, err return items, false, err
} }
return items, reset, last == "", nil return items, last == "", nil
} }
data.Value, data.Reset, data.End, err = loadStreamHandle() data.Value, data.End, err = loadStreamHandle()
data.Match, data.Decode, data.Format = param.MatchPattern, param.Decode, param.Format data.Decode, data.Format = param.Decode, param.Format
if err != nil { if err != nil {
resp.Msg = err.Error() resp.Msg = err.Error()
return return

View File

@ -21,8 +21,8 @@ type cliService struct {
} }
type cliOutput struct { type cliOutput struct {
Content []string `json:"content"` // output content Content string `json:"content"` // output content
Prompt string `json:"prompt,omitempty"` // new line prompt, empty if not ready to input Prompt string `json:"prompt,omitempty"` // new line prompt, empty if not ready to input
} }
var cli *cliService var cli *cliService
@ -54,7 +54,7 @@ func (c *cliService) runCommand(server, data string) {
} }
} }
c.echo(server, strutil.AnyToString(result, "", 0), true) c.echo(server, strutil.AnyToString(result), true)
} else { } else {
c.echoError(server, err.Error()) c.echoError(server, err.Error())
} }
@ -67,7 +67,7 @@ func (c *cliService) runCommand(server, data string) {
func (c *cliService) echo(server, data string, newLineReady bool) { func (c *cliService) echo(server, data string, newLineReady bool) {
output := cliOutput{ output := cliOutput{
Content: strings.Split(data, "\n"), Content: data,
} }
if newLineReady { if newLineReady {
output.Prompt = fmt.Sprintf("%s:db%d> ", server, c.selectedDB[server]) output.Prompt = fmt.Sprintf("%s:db%d> ", server, c.selectedDB[server])

View File

@ -9,6 +9,7 @@ import (
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"net" "net"
"net/url"
"os" "os"
"strings" "strings"
"sync" "sync"
@ -119,6 +120,7 @@ func (c *connectionService) buildOption(config types.ConnectionConfig) (*redis.O
} }
option := &redis.Options{ option := &redis.Options{
ClientName: url.QueryEscape(config.Name),
Addr: fmt.Sprintf("%s:%d", config.Addr, config.Port), Addr: fmt.Sprintf("%s:%d", config.Addr, config.Port),
Username: config.Username, Username: config.Username,
Password: config.Password, Password: config.Password,
@ -172,6 +174,7 @@ func (c *connectionService) createRedisClient(config types.ConnectionConfig) (re
//RouteByLatency: false, //RouteByLatency: false,
//RouteRandomly: false, //RouteRandomly: false,
//ClusterSlots: nil, //ClusterSlots: nil,
ClientName: url.QueryEscape(option.ClientName),
Dialer: option.Dialer, Dialer: option.Dialer,
OnConnect: option.OnConnect, OnConnect: option.OnConnect,
Protocol: option.Protocol, Protocol: option.Protocol,

View File

@ -35,8 +35,6 @@ type KeyDetail struct {
Length int64 `json:"length,omitempty"` Length int64 `json:"length,omitempty"`
Format string `json:"format,omitempty"` Format string `json:"format,omitempty"`
Decode string `json:"decode,omitempty"` Decode string `json:"decode,omitempty"`
Match string `json:"match,omitempty"`
Reset bool `json:"reset"`
End bool `json:"end"` End bool `json:"end"`
} }

View File

@ -6,7 +6,7 @@ import (
sliceutil "tinyrdm/backend/utils/slice" sliceutil "tinyrdm/backend/utils/slice"
) )
func AnyToString(value interface{}, prefix string, layer int) (s string) { func AnyToString(value interface{}) (s string) {
if value == nil { if value == nil {
return return
} }
@ -49,11 +49,7 @@ func AnyToString(value interface{}, prefix string, layer int) (s string) {
it := value.(uint64) it := value.(uint64)
s = strconv.FormatUint(it, 10) s = strconv.FormatUint(it, 10)
case string: case string:
if layer > 0 { s = value.(string)
s = "\"" + value.(string) + "\""
} else {
s = value.(string)
}
case bool: case bool:
val, _ := value.(bool) val, _ := value.(bool)
if val { if val {
@ -62,44 +58,24 @@ func AnyToString(value interface{}, prefix string, layer int) (s string) {
s = "False" s = "False"
} }
case []byte: case []byte:
s = prefix + string(value.([]byte)) s = string(value.([]byte))
case []string: case []string:
ss := value.([]string) ss := value.([]string)
anyStr := sliceutil.Map(ss, func(i int) string { anyStr := sliceutil.Map(ss, func(i int) string {
str := AnyToString(ss[i], prefix, layer+1) str := AnyToString(ss[i])
return prefix + strconv.Itoa(i+1) + ") " + str return strconv.Itoa(i+1) + ") \"" + str + "\""
}) })
s = prefix + sliceutil.JoinString(anyStr, "\r\n") s = sliceutil.JoinString(anyStr, "\r\n")
case []any: case []any:
as := value.([]any) as := value.([]any)
anyItems := sliceutil.Map(as, func(i int) string { anyItems := sliceutil.Map(as, func(i int) string {
str := AnyToString(as[i], prefix, layer+1) str := AnyToString(as[i])
return prefix + strconv.Itoa(i+1) + ") " + str return strconv.Itoa(i+1) + ") \"" + str + "\""
}) })
s = sliceutil.JoinString(anyItems, "\r\n") s = sliceutil.JoinString(anyItems, "\r\n")
case map[any]any:
am := value.(map[any]any)
var items []string
index := 0
for k, v := range am {
kk := prefix + strconv.Itoa(index+1) + ") " + AnyToString(k, prefix, layer+1)
vv := prefix + strconv.Itoa(index+2) + ") " + AnyToString(v, "\t", layer+1)
if layer > 0 {
indent := layer
if index == 0 {
indent -= 1
}
for i := 0; i < indent; i++ {
vv = " " + vv
}
}
index += 2
items = append(items, kk, vv)
}
s = sliceutil.JoinString(items, "\r\n")
default: default:
b, _ := json.Marshal(value) b, _ := json.Marshal(value)
s = prefix + string(b) s = string(b)
} }
return return

View File

@ -57,7 +57,6 @@ const tabContent = computed(() => {
length: tab.length || 0, length: tab.length || 0,
decode: tab.decode || decodeTypes.NONE, decode: tab.decode || decodeTypes.NONE,
format: tab.format || formatTypes.RAW, format: tab.format || formatTypes.RAW,
matchPattern: tab.matchPattern || '',
end: tab.end === true, end: tab.end === true,
loading: tab.loading === true, loading: tab.loading === true,
} }

View File

@ -203,7 +203,7 @@ const moveInputCursor = (step) => {
} else { } else {
// update cursor position only // update cursor position only
const currentLine = getCurrentInput() const currentLine = getCurrentInput()
inputCursor = Math.max(0, currentLine.length) inputCursor = Math.min(Math.max(0, inputCursor), currentLine.length)
updateCursor = true updateCursor = true
} }
@ -351,18 +351,16 @@ const replaceTermInput = (content = '') => {
/** /**
* process receive output content * process receive output content
* @param {{content: string[], prompt: string}} data * @param {{content, prompt}} data
*/ */
const receiveTermOutput = (data) => { const receiveTermOutput = (data) => {
if (termInst == null) { if (termInst == null) {
return return
} }
const { content = [], prompt } = data || {} const { content, prompt } = data || {}
if (!isEmpty(content)) { if (!isEmpty(content)) {
for (const line of content) { termInst.write('\r\n' + content)
termInst.write('\r\n' + line)
}
} }
if (!isEmpty(prompt)) { if (!isEmpty(prompt)) {
promptPrefix.value = prompt promptPrefix.value = prompt

View File

@ -1,84 +0,0 @@
<script setup>
import { computed, reactive } from 'vue'
import { debounce, isEmpty, trim } from 'lodash'
import { NButton, NInput } from 'naive-ui'
const emit = defineEmits(['filterChanged', 'matchChanged'])
/**
*
* @type {UnwrapNestedRefs<{filter: string, match: string}>}
*/
const inputData = reactive({
match: '',
filter: '',
})
const hasMatch = computed(() => {
return !isEmpty(trim(inputData.match))
})
const hasFilter = computed(() => {
return !isEmpty(trim(inputData.filter))
})
const onFullSearch = () => {
inputData.filter = trim(inputData.filter)
if (!isEmpty(inputData.filter)) {
inputData.match = inputData.filter
inputData.filter = ''
emit('matchChanged', inputData.match, inputData.filter)
}
}
const _onInput = () => {
emit('filterChanged', inputData.filter)
}
const onInput = debounce(_onInput, 500, { leading: true, trailing: true })
const onClearFilter = () => {
inputData.filter = ''
onClearMatch()
}
const onClearMatch = () => {
const changed = !isEmpty(inputData.match)
inputData.match = ''
if (changed) {
emit('matchChanged', inputData.match, inputData.filter)
} else {
emit('filterChanged', inputData.filter)
}
}
defineExpose({
reset: onClearFilter,
})
</script>
<template>
<n-input-group>
<n-input
v-model:value="inputData.filter"
:placeholder="$t('interface.filter')"
clearable
@clear="onClearFilter"
@input="onInput">
<template #prefix>
<n-tooltip v-if="hasMatch">
<template #trigger>
<n-tag closable size="small" @close="onClearMatch">
{{ inputData.match }}
</n-tag>
</template>
{{ $t('interface.full_search_result', { pattern: inputData.match }) }}
</n-tooltip>
</template>
</n-input>
<n-button :disabled="hasMatch && !hasFilter" :focusable="false" @click="onFullSearch">
{{ $t('interface.full_search') }}
</n-button>
</n-input-group>
</template>
<style lang="scss" scoped></style>

View File

@ -3,7 +3,7 @@ import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue' import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NCode, NIcon, useThemeVars } from 'naive-ui' 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'
@ -18,7 +18,6 @@ import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vu
import Edit from '@/components/icons/Edit.vue' import Edit from '@/components/icons/Edit.vue'
import FormatSelector from '@/components/content_value/FormatSelector.vue' import FormatSelector from '@/components/content_value/FormatSelector.vue'
import { decodeRedisKey } from '@/utils/key_convert.js' import { decodeRedisKey } from '@/utils/key_convert.js'
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
const i18n = useI18n() const i18n = useI18n()
const themeVars = useThemeVars() const themeVars = useThemeVars()
@ -53,7 +52,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
/** /**
* *
@ -63,6 +62,18 @@ const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
}) })
const filterOption = [
{
value: 1,
label: i18n.t('common.field'),
},
{
value: 2,
label: i18n.t('common.value'),
},
]
const filterType = ref(1)
const browserStore = useBrowserStore() const browserStore = useBrowserStore()
const dialogStore = useDialogStore() const dialogStore = useDialogStore()
const keyType = redisTypes.HASH const keyType = redisTypes.HASH
@ -103,7 +114,7 @@ const fieldColumn = computed(() => ({
const displayCode = computed(() => { const displayCode = computed(() => {
return props.format === formatTypes.JSON return props.format === formatTypes.JSON
}) })
// const valueFilterOption = ref(null) const valueFilterOption = ref(null)
const valueColumn = computed(() => ({ const valueColumn = computed(() => ({
key: 'value', key: 'value',
title: i18n.t('common.value'), title: i18n.t('common.value'),
@ -115,14 +126,14 @@ const valueColumn = computed(() => ({
: { : {
tooltip: true, tooltip: true,
}, },
// filterOptionValue: valueFilterOption.value, filterOptionValue: valueFilterOption.value,
className: inEdit.value ? 'clickable' : '', className: inEdit.value ? 'clickable' : '',
// filter: (value, row) => { filter: (value, row) => {
// if (row.dv) { if (row.dv) {
// return !!~row.dv.indexOf(value.toString()) return !!~row.dv.indexOf(value.toString())
// } }
// return !!~row.v.indexOf(value.toString()) return !!~row.v.indexOf(value.toString())
// }, },
render: (row) => { render: (row) => {
if (displayCode.value) { if (displayCode.value) {
return h(NCode, { language: 'json', wordWrap: true, code: row.dv || row.v }) return h(NCode, { language: 'json', wordWrap: true, code: row.dv || row.v })
@ -272,28 +283,50 @@ 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)
} }
const filterValue = ref('')
const onFilterInput = (val) => { const onFilterInput = (val) => {
fieldFilterOption.value = val switch (filterType.value) {
case filterOption[0].value:
// filter field
valueFilterOption.value = null
fieldFilterOption.value = val
break
case filterOption[1].value:
// filter value
fieldFilterOption.value = null
valueFilterOption.value = val
break
}
} }
const onMatchInput = (matchVal, filterVal) => { const onChangeFilterType = (type) => {
fieldFilterOption.value = filterVal onFilterInput(filterValue.value)
emit('match', matchVal) }
const clearFilter = () => {
fieldFilterOption.value = null
valueFilterOption.value = null
} }
const onUpdateFilter = (filters, sourceColumn) => { const onUpdateFilter = (filters, sourceColumn) => {
fieldFilterOption.value = filters[sourceColumn.key] switch (filterType.value) {
case filterOption[0].value:
fieldFilterOption.value = filters[sourceColumn.key]
break
case filterOption[1].value:
valueFilterOption.value = filters[sourceColumn.key]
break
}
} }
const onFormatChanged = (selDecode, selFormat) => { const onFormatChanged = (selDecode, selFormat) => {
emit('reload', selDecode, selFormat) emit('reload', selDecode, selFormat)
} }
const searchInputRef = ref(null)
defineExpose({ defineExpose({
reset: () => { reset: () => {
clearFilter()
resetEdit() resetEdit()
searchInputRef.value?.reset()
}, },
}) })
</script> </script>
@ -314,10 +347,20 @@ defineExpose({
@rename="emit('rename')" /> @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">
<content-search-input <n-input-group>
ref="searchInputRef" <n-select
@filter-changed="onFilterInput" v-model:value="filterType"
@match-changed="onMatchInput" /> :consistent-menu-width="false"
:options="filterOption"
style="width: 120px"
@update:value="onChangeFilterType" />
<n-input
v-model:value="filterValue"
:placeholder="$t('interface.search')"
clearable
@clear="clearFilter"
@update:value="onFilterInput" />
</n-input-group>
</div> </div>
<div class="flex-item-expand"></div> <div class="flex-item-expand"></div>
<n-button-group> <n-button-group>

View File

@ -3,7 +3,7 @@ import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue' import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NCode, NIcon, useThemeVars } from 'naive-ui' import { NButton, NCode, NIcon, NInput, useThemeVars } from 'naive-ui'
import { isEmpty, size } from 'lodash' import { isEmpty, size } from 'lodash'
import { types, types as redisTypes } from '@/consts/support_redis_type.js' import { types, types as redisTypes } from '@/consts/support_redis_type.js'
import EditableTableColumn from '@/components/common/EditableTableColumn.vue' import EditableTableColumn from '@/components/common/EditableTableColumn.vue'
@ -17,7 +17,6 @@ import IconButton from '@/components/common/IconButton.vue'
import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue' import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue'
import FormatSelector from '@/components/content_value/FormatSelector.vue' import FormatSelector from '@/components/content_value/FormatSelector.vue'
import Edit from '@/components/icons/Edit.vue' import Edit from '@/components/icons/Edit.vue'
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
const i18n = useI18n() const i18n = useI18n()
const themeVars = useThemeVars() const themeVars = useThemeVars()
@ -53,7 +52,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
/** /**
* *
@ -250,13 +249,13 @@ 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)
} }
const filterValue = ref('')
const onFilterInput = (val) => { const onFilterInput = (val) => {
valueFilterOption.value = val valueFilterOption.value = val
} }
const onMatchInput = (matchVal, filterVal) => { const clearFilter = () => {
valueFilterOption.value = filterVal valueFilterOption.value = null
emit('match', matchVal)
} }
const onUpdateFilter = (filters, sourceColumn) => { const onUpdateFilter = (filters, sourceColumn) => {
@ -267,11 +266,10 @@ const onFormatChanged = (selDecode, selFormat) => {
emit('reload', selDecode, selFormat) emit('reload', selDecode, selFormat)
} }
const searchInputRef = ref(null)
defineExpose({ defineExpose({
reset: () => { reset: () => {
clearFilter()
resetEdit() resetEdit()
searchInputRef.value?.reset()
}, },
}) })
</script> </script>
@ -292,10 +290,12 @@ defineExpose({
@rename="emit('rename')" /> @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">
<content-search-input <n-input
ref="searchInputRef" v-model:value="filterValue"
@filter-changed="onFilterInput" :placeholder="$t('interface.search')"
@match-changed="onMatchInput" /> clearable
@clear="clearFilter"
@update:value="onFilterInput" />
</div> </div>
<div class="flex-item-expand"></div> <div class="flex-item-expand"></div>
<n-button-group> <n-button-group>

View File

@ -3,7 +3,7 @@ import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue' import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NCode, NIcon, useThemeVars } from 'naive-ui' import { NButton, NCode, NIcon, NInput, useThemeVars } from 'naive-ui'
import { isEmpty, size } from 'lodash' import { isEmpty, size } from 'lodash'
import useDialogStore from 'stores/dialog.js' import useDialogStore from 'stores/dialog.js'
import { types, types as redisTypes } from '@/consts/support_redis_type.js' import { types, types as redisTypes } from '@/consts/support_redis_type.js'
@ -17,7 +17,6 @@ import IconButton from '@/components/common/IconButton.vue'
import Edit from '@/components/icons/Edit.vue' import Edit from '@/components/icons/Edit.vue'
import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue' import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue'
import FormatSelector from '@/components/content_value/FormatSelector.vue' import FormatSelector from '@/components/content_value/FormatSelector.vue'
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
const i18n = useI18n() const i18n = useI18n()
const themeVars = useThemeVars() const themeVars = useThemeVars()
@ -52,7 +51,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
/** /**
* *
@ -247,13 +246,13 @@ 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)
} }
const filterValue = ref('')
const onFilterInput = (val) => { const onFilterInput = (val) => {
valueFilterOption.value = val valueFilterOption.value = val
} }
const onMatchInput = (matchVal, filterVal) => { const clearFilter = () => {
valueFilterOption.value = filterVal valueFilterOption.value = null
emit('match', matchVal)
} }
const onUpdateFilter = (filters, sourceColumn) => { const onUpdateFilter = (filters, sourceColumn) => {
@ -264,11 +263,10 @@ const onFormatChanged = (selDecode, selFormat) => {
emit('reload', selDecode, selFormat) emit('reload', selDecode, selFormat)
} }
const searchInputRef = ref(null)
defineExpose({ defineExpose({
reset: () => { reset: () => {
clearFilter()
resetEdit() resetEdit()
searchInputRef.value?.reset()
}, },
}) })
</script> </script>
@ -289,10 +287,12 @@ defineExpose({
@rename="emit('rename')" /> @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">
<content-search-input <n-input
ref="searchInputRef" v-model:value="filterValue"
@filter-changed="onFilterInput" :placeholder="$t('interface.search')"
@match-changed="onMatchInput" /> clearable
@clear="clearFilter"
@update:value="onFilterInput" />
</div> </div>
<div class="flex-item-expand"></div> <div class="flex-item-expand"></div>
<n-button-group> <n-button-group>

View File

@ -3,18 +3,17 @@ import { computed, h, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue' import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NCode, NIcon, useThemeVars } from 'naive-ui' 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, size } 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 LoadList from '@/components/icons/LoadList.vue'
import LoadAll from '@/components/icons/LoadAll.vue' import LoadAll from '@/components/icons/LoadAll.vue'
import IconButton from '@/components/common/IconButton.vue' import IconButton from '@/components/common/IconButton.vue'
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
const i18n = useI18n() const i18n = useI18n()
const themeVars = useThemeVars() const themeVars = useThemeVars()
@ -49,7 +48,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
/** /**
* *
@ -58,17 +57,30 @@ const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', '
const keyName = computed(() => { const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
}) })
const filterOption = [
{
value: 1,
label: i18n.t('common.field'),
},
{
value: 2,
label: i18n.t('common.value'),
},
]
const filterType = ref(1) const filterType = ref(1)
const browserStore = useBrowserStore() const browserStore = useBrowserStore()
const dialogStore = useDialogStore() const dialogStore = useDialogStore()
const keyType = redisTypes.STREAM const keyType = redisTypes.STREAM
const idFilterOption = ref(null)
const idColumn = computed(() => ({ const idColumn = computed(() => ({
key: 'id', key: 'id',
title: 'ID', title: 'ID',
align: 'center', align: 'center',
titleAlign: 'center', titleAlign: 'center',
resizable: true, resizable: true,
filterOptionValue: idFilterOption.value,
})) }))
const valueFilterOption = ref(null) const valueFilterOption = ref(null)
@ -81,15 +93,13 @@ const valueColumn = computed(() => ({
filterOptionValue: valueFilterOption.value, filterOptionValue: valueFilterOption.value,
filter: (value, row) => { filter: (value, row) => {
const v = value.toString() const v = value.toString()
if (row.dv) { if (filterType.value === 1) {
return includes(row.dv, v) // filter key
return some(keys(row.v), (key) => includes(key, v))
} else {
// filter value
return some(values(row.v), (val) => includes(val, v))
} }
for (const k in row.v) {
if (includes(k, v) || includes(row.v[k], v)) {
return true
}
}
return false
}, },
// sorter: (row1, row2) => row1.value - row2.value, // sorter: (row1, row2) => row1.value - row2.value,
render: (row) => { render: (row) => {
@ -138,23 +148,34 @@ 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)
} }
const filterValue = ref('')
const onFilterInput = (val) => { const onFilterInput = (val) => {
valueFilterOption.value = val valueFilterOption.value = val
} }
const onMatchInput = (matchVal, filterVal) => { const onChangeFilterType = (type) => {
valueFilterOption.value = filterVal onFilterInput(filterValue.value)
emit('match', matchVal) }
const clearFilter = () => {
idFilterOption.value = null
valueFilterOption.value = null
} }
const onUpdateFilter = (filters, sourceColumn) => { const onUpdateFilter = (filters, sourceColumn) => {
valueFilterOption.value = filters[sourceColumn.key] switch (filterType.value) {
case filterOption[0].value:
idFilterOption.value = filters[sourceColumn.key]
break
case filterOption[1].value:
valueFilterOption.value = filters[sourceColumn.key]
break
}
} }
const searchInputRef = ref(null)
defineExpose({ defineExpose({
reset: () => { reset: () => {
searchInputRef.value?.reset() clearFilter()
}, },
}) })
</script> </script>
@ -175,24 +196,20 @@ defineExpose({
@rename="emit('rename')" /> @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>
<!-- <n-select--> <n-select
<!-- v-model:value="filterType"--> v-model:value="filterType"
<!-- :consistent-menu-width="false"--> :consistent-menu-width="false"
<!-- :options="filterOption"--> :options="filterOption"
<!-- style="width: 120px"--> style="width: 120px"
<!-- @update:value="onChangeFilterType" />--> @update:value="onChangeFilterType" />
<!-- <n-input--> <n-input
<!-- v-model:value="filterValue"--> v-model:value="filterValue"
<!-- :placeholder="$t('interface.search')"--> :placeholder="$t('interface.search')"
<!-- clearable--> clearable
<!-- @clear="clearFilter"--> @clear="clearFilter"
<!-- @update:value="onFilterInput" />--> @update:value="onFilterInput" />
<!-- </n-input-group>--> </n-input-group>
<content-search-input
ref="searchInputRef"
@filter-changed="onFilterInput"
@match-changed="onMatchInput" />
</div> </div>
<div class="flex-item-expand"></div> <div class="flex-item-expand"></div>
<n-button-group> <n-button-group>

View File

@ -64,14 +64,7 @@ const keyName = computed(() => {
return !isEmpty(data.value.keyCode) ? data.value.keyCode : data.value.keyPath return !isEmpty(data.value.keyCode) ? data.value.keyCode : data.value.keyPath
}) })
/** const loadData = async (reset, full) => {
*
* @param {boolean} reset
* @param {boolean} [full]
* @param {string} [selMatch]
* @return {Promise<void>}
*/
const loadData = async (reset, full, selMatch) => {
try { try {
if (!!props.blank) { if (!!props.blank) {
return return
@ -82,7 +75,7 @@ const loadData = async (reset, full, selMatch) => {
server: name, server: name,
db: db, db: db,
key: keyName.value, key: keyName.value,
matchPattern: selMatch === undefined ? matchPattern : selMatch, matchPattern: matchPattern,
decode: reset ? decodeTypes.NONE : decode, decode: reset ? decodeTypes.NONE : decode,
format: reset ? formatTypes.RAW : format, format: reset ? formatTypes.RAW : format,
reset, reset,
@ -100,14 +93,13 @@ const loadData = async (reset, full, selMatch) => {
*/ */
const onReload = async (selDecode, selFormat) => { const onReload = async (selDecode, selFormat) => {
try { try {
const { name, db, keyCode, keyPath, decode, format, matchPattern } = data.value const { name, db, keyCode, keyPath, decode, format } = data.value
await browserStore.reloadKey({ await browserStore.reloadKey({
server: name, server: name,
db, db,
key: keyCode || keyPath, key: keyCode || keyPath,
decode: selDecode || decode, decode: selDecode || decode,
format: selFormat || format, format: selFormat || format,
matchPattern,
}) })
} finally { } finally {
} }
@ -141,10 +133,6 @@ const onLoadAll = () => {
loadData(false, true) loadData(false, true)
} }
const onMatch = (match) => {
loadData(true, false, match || '')
}
const contentRef = ref(null) const contentRef = ref(null)
const initContent = async () => { const initContent = async () => {
// onReload() // onReload()
@ -153,7 +141,7 @@ const initContent = async () => {
if (contentRef.value?.reset != null) { if (contentRef.value?.reset != null) {
contentRef.value?.reset() contentRef.value?.reset()
} }
await loadData(true, false, '') await loadData(true, false)
if (contentRef.value?.beforeShow != null) { if (contentRef.value?.beforeShow != null) {
await contentRef.value?.beforeShow() await contentRef.value?.beforeShow()
} }
@ -177,7 +165,7 @@ watch(() => data.value?.keyPath, initContent)
</template> </template>
</n-empty> </n-empty>
<!-- FIXME: keep alive may cause virtual list null value error. --> <!-- FIXME: keep alive may cause virtual list null value error. -->
<!-- <keep-alive v-else> --> <!-- <keep-alive v-else>-->
<component <component
:is="valueComponents[data.type]" :is="valueComponents[data.type]"
v-else v-else
@ -197,7 +185,6 @@ watch(() => data.value?.keyPath, initContent)
@delete="onDelete" @delete="onDelete"
@loadall="onLoadAll" @loadall="onLoadAll"
@loadmore="onLoadMore" @loadmore="onLoadMore"
@match="onMatch"
@reload="onReload" @reload="onReload"
@rename="onRename" /> @rename="onRename" />
<!-- </keep-alive>--> <!-- </keep-alive>-->

View File

@ -3,7 +3,7 @@ import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue' import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NCode, NIcon, useThemeVars } from 'naive-ui' 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 { isEmpty, size } from 'lodash' import { isEmpty, size } from 'lodash'
@ -17,7 +17,6 @@ import IconButton from '@/components/common/IconButton.vue'
import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue' import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue'
import FormatSelector from '@/components/content_value/FormatSelector.vue' import FormatSelector from '@/components/content_value/FormatSelector.vue'
import Edit from '@/components/icons/Edit.vue' import Edit from '@/components/icons/Edit.vue'
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
const i18n = useI18n() const i18n = useI18n()
const themeVars = useThemeVars() const themeVars = useThemeVars()
@ -52,7 +51,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete'])
/** /**
* *
@ -62,6 +61,18 @@ const keyName = computed(() => {
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
}) })
const filterOption = [
{
value: 1,
label: i18n.t('common.value'),
},
{
value: 2,
label: i18n.t('common.score'),
},
]
const filterType = ref(1)
const browserStore = useBrowserStore() const browserStore = useBrowserStore()
const dialogStore = useDialogStore() const dialogStore = useDialogStore()
const keyType = redisTypes.ZSET const keyType = redisTypes.ZSET
@ -78,46 +89,46 @@ const inEdit = computed(() => {
}) })
const fullEdit = ref(false) const fullEdit = ref(false)
// const scoreFilterOption = ref(null) const scoreFilterOption = ref(null)
const scoreColumn = computed(() => ({ const scoreColumn = computed(() => ({
key: 'score', key: 'score',
title: i18n.t('common.score'), title: i18n.t('common.score'),
align: 'center', align: 'center',
titleAlign: 'center', titleAlign: 'center',
resizable: true, resizable: true,
// filterOptionValue: scoreFilterOption.value, filterOptionValue: scoreFilterOption.value,
// filter(value, row) { filter(value, row) {
// const score = parseFloat(row.s) const score = parseFloat(row.s)
// if (isNaN(score)) { if (isNaN(score)) {
// return true return true
// } }
//
// const regex = /^(>=|<=|>|<|=|!=)?(\d+(\.\d*)?)?$/ const regex = /^(>=|<=|>|<|=|!=)?(\d+(\.\d*)?)?$/
// const matches = value.match(regex) const matches = value.match(regex)
// if (matches) { if (matches) {
// const operator = matches[1] || '' const operator = matches[1] || ''
// const filterScore = parseFloat(matches[2] || '') const filterScore = parseFloat(matches[2] || '')
// if (!isNaN(filterScore)) { if (!isNaN(filterScore)) {
// switch (operator) { switch (operator) {
// case '>=': case '>=':
// return score >= filterScore return score >= filterScore
// case '<=': case '<=':
// return score <= filterScore return score <= filterScore
// case '>': case '>':
// return score > filterScore return score > filterScore
// case '<': case '<':
// return score < filterScore return score < filterScore
// case '=': case '=':
// return score === filterScore return score === filterScore
// case '!=': case '!=':
// return score !== filterScore return score !== filterScore
// } }
// } }
// } else { } else {
// return !!~row.v.indexOf(value.toString()) return !!~row.v.indexOf(value.toString())
// } }
// return true return true
// }, },
render: (row) => { render: (row) => {
return row.s return row.s
}, },
@ -283,28 +294,52 @@ 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)
} }
const filterValue = ref('')
const onFilterInput = (val) => { const onFilterInput = (val) => {
valueFilterOption.value = val switch (filterType.value) {
case filterOption[0].value:
// filter value
scoreFilterOption.value = null
valueFilterOption.value = val
break
case filterOption[1].value:
// filter score
valueFilterOption.value = null
scoreFilterOption.value = val
break
}
} }
const onMatchInput = (matchVal, filterVal) => { const onChangeFilterType = (type) => {
valueFilterOption.value = filterVal onFilterInput(filterValue.value)
emit('match', matchVal) }
const clearFilter = () => {
valueFilterOption.value = null
scoreFilterOption.value = null
} }
const onUpdateFilter = (filters, sourceColumn) => { const onUpdateFilter = (filters, sourceColumn) => {
valueFilterOption.value = filters[sourceColumn.key] switch (filterType.value) {
case filterOption[0].value:
// filter value
valueFilterOption.value = filters[sourceColumn.key]
break
case filterOption[1].value:
// filter score
scoreFilterOption.value = filters[sourceColumn.key]
break
}
} }
const onFormatChanged = (selDecode, selFormat) => { const onFormatChanged = (selDecode, selFormat) => {
emit('reload', selDecode, selFormat) emit('reload', selDecode, selFormat)
} }
const searchInputRef = ref(null)
defineExpose({ defineExpose({
reset: () => { reset: () => {
clearFilter()
resetEdit() resetEdit()
searchInputRef.value?.reset()
}, },
}) })
</script> </script>
@ -325,10 +360,25 @@ defineExpose({
@rename="emit('rename')" /> @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">
<content-search-input <n-input-group>
ref="searchInputRef" <n-select
@filter-changed="onFilterInput" v-model:value="filterType"
@match-changed="onMatchInput" /> :consistent-menu-width="false"
:options="filterOption"
style="width: 120px"
@update:value="onChangeFilterType" />
<n-tooltip :delay="500" :disabled="filterType !== 2">
<template #trigger>
<n-input
v-model:value="filterValue"
:placeholder="$t('interface.search')"
clearable
@clear="clearFilter"
@update:value="onFilterInput" />
</template>
<div class="text-block">{{ $t('interface.score_filter_tip') }}</div>
</n-tooltip>
</n-input-group>
</div> </div>
<div class="flex-item-expand"></div> <div class="flex-item-expand"></div>
<n-button-group> <n-button-group>

View File

@ -120,10 +120,8 @@ const onLoadSentinelMasters = async () => {
const tab = ref('general') const tab = ref('general')
const testing = ref(false) const testing = ref(false)
const testResult = ref(null) const showTestResult = ref(false)
const showTestResult = computed(() => { const testResult = ref('')
return !testing.value && testResult.value != null
})
const predefineColors = ref(['', '#F75B52', '#F7A234', '#F7CE33', '#4ECF60', '#348CF7', '#B270D3']) const predefineColors = ref(['', '#F75B52', '#F7A234', '#F7CE33', '#4ECF60', '#348CF7', '#B270D3'])
const generalFormRef = ref(null) const generalFormRef = ref(null)
const advanceFormRef = ref(null) const advanceFormRef = ref(null)
@ -196,7 +194,8 @@ const resetForm = () => {
generalForm.value = connectionStore.newDefaultConnection() generalForm.value = connectionStore.newDefaultConnection()
generalFormRef.value?.restoreValidation() generalFormRef.value?.restoreValidation()
testing.value = false testing.value = false
testResult.value = null showTestResult.value = false
testResult.value = ''
tab.value = 'general' tab.value = 'general'
loadingSentinelMaster.value = false loadingSentinelMaster.value = false
} }
@ -227,6 +226,7 @@ const onTestConnection = async () => {
result = e.message result = e.message
} finally { } finally {
testing.value = false testing.value = false
showTestResult.value = true
} }
if (!isEmpty(result)) { if (!isEmpty(result)) {
@ -566,7 +566,7 @@ const onClose = () => {
<!-- test result alert--> <!-- test result alert-->
<n-alert <n-alert
v-if="showTestResult" v-if="showTestResult"
:on-close="() => (testResult = '')" :on-close="() => (showTestResult = false)"
:title="isEmpty(testResult) ? '' : $t('dialogue.connection.test_fail')" :title="isEmpty(testResult) ? '' : $t('dialogue.connection.test_fail')"
:type="isEmpty(testResult) ? 'success' : 'error'" :type="isEmpty(testResult) ? 'success' : 'error'"
closable> closable>

View File

@ -81,8 +81,6 @@
"pin_edit": "Pin Editor(Do not close after save)", "pin_edit": "Pin Editor(Do not close after save)",
"unpin_edit": "Cancel Pin", "unpin_edit": "Cancel Pin",
"search": "Search", "search": "Search",
"full_search": "Full Search",
"full_search_result": "The content has been matched as '*{pattern}*'",
"filter_field": "Filter Field", "filter_field": "Filter Field",
"filter_value": "Filter Value", "filter_value": "Filter Value",
"length": "Length", "length": "Length",

View File

@ -81,8 +81,6 @@
"pin_edit": "固定编辑框(保存后不关闭)", "pin_edit": "固定编辑框(保存后不关闭)",
"unpin_edit": "取消固定", "unpin_edit": "取消固定",
"search": "搜索", "search": "搜索",
"full_search": "全文匹配",
"full_search_result": "内容已匹配为 *{pattern}*",
"filter_field": "筛选字段", "filter_field": "筛选字段",
"filter_value": "筛选值", "filter_value": "筛选值",
"length": "长度", "length": "长度",

View File

@ -429,15 +429,14 @@ const useBrowserStore = defineStore('browser', {
* @param {string|number[]} key * @param {string|number[]} key
* @param {string} [decode] * @param {string} [decode]
* @param {string} [format] * @param {string} [format]
* @param {string} [matchPattern]
* @return {Promise<void>} * @return {Promise<void>}
*/ */
async reloadKey({ server, db, key, decode, format, matchPattern }) { async reloadKey({ server, db, key, decode, format }) {
const tab = useTabStore() const tab = useTabStore()
try { try {
tab.updateLoading({ server, db, loading: true }) tab.updateLoading({ server, db, loading: true })
await this.loadKeySummary({ server, db, key }) await this.loadKeySummary({ server, db, key })
await this.loadKeyDetail({ server, db, key, decode, format, matchPattern, reset: true }) await this.loadKeyDetail({ server, db, key, decode, format, reset: true })
} finally { } finally {
tab.updateLoading({ server, db, loading: false }) tab.updateLoading({ server, db, loading: false })
} }
@ -471,7 +470,7 @@ const useBrowserStore = defineStore('browser', {
lite: true, lite: true,
}) })
if (success) { if (success) {
const { value, decode: retDecode, format: retFormat, match: retMatch, reset: retReset, end } = data const { value, decode: retDecode, format: retFormat, end } = data
tab.updateValue({ tab.updateValue({
server, server,
db, db,
@ -479,8 +478,7 @@ const useBrowserStore = defineStore('browser', {
value, value,
decode: retDecode, decode: retDecode,
format: retFormat, format: retFormat,
reset: retReset, reset: reset || full === true,
matchPattern: retMatch || '',
end, end,
}) })
} else { } else {

View File

@ -21,7 +21,6 @@ const useTabStore = defineStore('tab', {
* @property {int} [ttl] ttl of current key * @property {int} [ttl] ttl of current key
* @param {string} [decode] * @param {string} [decode]
* @param {string} [format] * @param {string} [format]
* @param {string} [matchPattern]
* @param {boolean} [end] * @param {boolean} [end]
* @param {boolean} [loading] * @param {boolean} [loading]
*/ */
@ -160,10 +159,9 @@ const useTabStore = defineStore('tab', {
* @param {string} [keyCode] * @param {string} [keyCode]
* @param {number} [size] * @param {number} [size]
* @param {number} [length] * @param {number} [length]
* @param {string} [matchPattern]
* @param {*} [value] * @param {*} [value]
*/ */
upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, length, matchPattern = '' }) { 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({
@ -178,7 +176,6 @@ const useTabStore = defineStore('tab', {
keyCode, keyCode,
size, size,
length, length,
matchPattern,
value: undefined, value: undefined,
}) })
tabIndex = this.tabList.length - 1 tabIndex = this.tabList.length - 1
@ -196,7 +193,6 @@ const useTabStore = defineStore('tab', {
tab.keyCode = keyCode tab.keyCode = keyCode
tab.size = size tab.size = size
tab.length = length tab.length = length
tab.matchPattern = matchPattern
tab.value = undefined tab.value = undefined
} }
this._setActivatedIndex(tabIndex, true, subTab) this._setActivatedIndex(tabIndex, true, subTab)
@ -211,31 +207,29 @@ const useTabStore = defineStore('tab', {
* @param {*} value * @param {*} value
* @param {string} [format] * @param {string} [format]
* @param {string] [decode] * @param {string] [decode]
* @param {string} [matchPattern]
* @param {boolean} reset * @param {boolean} reset
* @param {boolean} [end] keep end status if not set * @param {boolean} [end] keep end status if not set
*/ */
updateValue({ server, db, key, value, format, decode, matchPattern, reset, end }) { updateValue({ server, db, key, value, format, decode, reset, end }) {
const tabData = find(this.tabList, { name: server, db, key }) const tab = find(this.tabList, { name: server, db, key })
if (tabData == null) { if (tab == null) {
return return
} }
tabData.format = format || tabData.format tab.format = format || tab.format
tabData.decode = decode || tabData.decode tab.decode = decode || tab.decode
tabData.matchPattern = matchPattern || ''
if (typeof end === 'boolean') { if (typeof end === 'boolean') {
tabData.end = end tab.end = end
} }
if (!reset && typeof value === 'object') { if (!reset && typeof value === 'object') {
if (value instanceof Array) { if (value instanceof Array) {
tabData.value = tabData.value || [] tab.value = tab.value || []
tabData.value.push(...value) tab.value.push(...value)
} else { } else {
tabData.value = assign(value, tabData.value || {}) tab.value = assign(value, tab.value || {})
} }
} else { } else {
tabData.value = value tab.value = value
} }
}, },