refactor: move the handling of String viewing methods to backend
feat: add view as gzip/deflate #30
This commit is contained in:
parent
7aba27e5f9
commit
ffc50a7fcc
|
@ -17,6 +17,7 @@ import (
|
||||||
"tinyrdm/backend/types"
|
"tinyrdm/backend/types"
|
||||||
maputil "tinyrdm/backend/utils/map"
|
maputil "tinyrdm/backend/utils/map"
|
||||||
redis2 "tinyrdm/backend/utils/redis"
|
redis2 "tinyrdm/backend/utils/redis"
|
||||||
|
strutil "tinyrdm/backend/utils/string"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cmdHistoryItem struct {
|
type cmdHistoryItem struct {
|
||||||
|
@ -476,7 +477,7 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyValue get value by key
|
// GetKeyValue get value by key
|
||||||
func (c *connectionService) GetKeyValue(connName string, db int, key string) (resp types.JSResp) {
|
func (c *connectionService) GetKeyValue(connName string, db int, key, viewAs string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
rdb, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -512,7 +513,9 @@ func (c *connectionService) GetKeyValue(connName string, db int, key string) (re
|
||||||
var cursor uint64
|
var cursor uint64
|
||||||
switch strings.ToLower(keyType) {
|
switch strings.ToLower(keyType) {
|
||||||
case "string":
|
case "string":
|
||||||
value, err = rdb.Get(ctx, key).Result()
|
var str string
|
||||||
|
str, err = rdb.Get(ctx, key).Result()
|
||||||
|
value, viewAs = strutil.ConvertTo(str, viewAs)
|
||||||
size, _ = rdb.StrLen(ctx, key).Result()
|
size, _ = rdb.StrLen(ctx, key).Result()
|
||||||
case "list":
|
case "list":
|
||||||
value, err = rdb.LRange(ctx, key, 0, -1).Result()
|
value, err = rdb.LRange(ctx, key, 0, -1).Result()
|
||||||
|
@ -605,13 +608,14 @@ func (c *connectionService) GetKeyValue(connName string, db int, key string) (re
|
||||||
"ttl": ttl,
|
"ttl": ttl,
|
||||||
"value": value,
|
"value": value,
|
||||||
"size": size,
|
"size": size,
|
||||||
|
"viewAs": viewAs,
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetKeyValue set value by key
|
// SetKeyValue set value by key
|
||||||
// @param ttl <= 0 means keep current ttl
|
// @param ttl <= 0 means keep current ttl
|
||||||
func (c *connectionService) SetKeyValue(connName string, db int, key, keyType string, value any, ttl int64) (resp types.JSResp) {
|
func (c *connectionService) SetKeyValue(connName string, db int, key, keyType string, value any, ttl int64, viewAs string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
rdb, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -632,7 +636,12 @@ func (c *connectionService) SetKeyValue(connName string, db int, key, keyType st
|
||||||
resp.Msg = "invalid string value"
|
resp.Msg = "invalid string value"
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
_, err = rdb.Set(ctx, key, str, 0).Result()
|
var saveStr string
|
||||||
|
if saveStr, err = strutil.SaveAs(str, viewAs); err != nil {
|
||||||
|
resp.Msg = fmt.Sprintf(`save to "%s" type fail: %s`, viewAs, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = rdb.Set(ctx, key, saveStr, 0).Result()
|
||||||
// set expiration lonely, not "keepttl"
|
// set expiration lonely, not "keepttl"
|
||||||
if err == nil && expiration > 0 {
|
if err == nil && expiration > 0 {
|
||||||
rdb.Expire(ctx, key, expiration)
|
rdb.Expire(ctx, key, expiration)
|
||||||
|
|
|
@ -104,10 +104,8 @@ func (p *preferencesService) GetAppVersion() (resp types.JSResp) {
|
||||||
|
|
||||||
func (p *preferencesService) SaveWindowSize(width, height int) {
|
func (p *preferencesService) SaveWindowSize(width, height int) {
|
||||||
p.SetPreferences(map[string]any{
|
p.SetPreferences(map[string]any{
|
||||||
"behavior": map[string]any{
|
"behavior.window_width": width,
|
||||||
"window_width": width,
|
"behavior.window_height": height,
|
||||||
"window_height": height,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package storage
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"tinyrdm/backend/consts"
|
"tinyrdm/backend/consts"
|
||||||
|
@ -158,10 +159,12 @@ func (p *PreferencesStorage) SetPreferencesN(values map[string]any) error {
|
||||||
|
|
||||||
pf := p.getPreferences()
|
pf := p.getPreferences()
|
||||||
for path, v := range values {
|
for path, v := range values {
|
||||||
|
log.Println("path", path, v)
|
||||||
if err := p.setPreferences(pf, path, v); err != nil {
|
if err := p.setPreferences(pf, path, v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Println("after save", pf)
|
||||||
|
|
||||||
return p.savePreferences(pf)
|
return p.savePreferences(pf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
const PLAIN_TEXT = "Plain Text"
|
||||||
|
const JSON = "JSON"
|
||||||
|
const BASE64_TEXT = "Base64 Text"
|
||||||
|
const BASE64_JSON = "Base64 JSON"
|
||||||
|
const HEX = "Hex"
|
||||||
|
const BINARY = "Binary"
|
||||||
|
const GZIP = "GZip"
|
||||||
|
const GZIP_JSON = "GZip JSON"
|
||||||
|
const DEFLATE = "Deflate"
|
|
@ -0,0 +1,275 @@
|
||||||
|
package strutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/flate"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"tinyrdm/backend/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertTo convert string to specified type
|
||||||
|
// @param targetType empty string indicates automatic detection of the string type
|
||||||
|
func ConvertTo(str, targetType string) (value, resultType string) {
|
||||||
|
if len(str) <= 0 {
|
||||||
|
// empty content
|
||||||
|
if len(targetType) <= 0 {
|
||||||
|
resultType = types.PLAIN_TEXT
|
||||||
|
} else {
|
||||||
|
resultType = targetType
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch targetType {
|
||||||
|
case types.PLAIN_TEXT:
|
||||||
|
value = str
|
||||||
|
resultType = targetType
|
||||||
|
return
|
||||||
|
|
||||||
|
case types.JSON:
|
||||||
|
value, _ = decodeJson(str)
|
||||||
|
resultType = targetType
|
||||||
|
return
|
||||||
|
|
||||||
|
case types.BASE64_TEXT, types.BASE64_JSON:
|
||||||
|
if base64Str, ok := decodeBase64(str); ok {
|
||||||
|
if targetType == types.BASE64_JSON {
|
||||||
|
value, _ = decodeJson(base64Str)
|
||||||
|
} else {
|
||||||
|
value = base64Str
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = str
|
||||||
|
}
|
||||||
|
resultType = targetType
|
||||||
|
return
|
||||||
|
|
||||||
|
case types.HEX:
|
||||||
|
if hexStr, ok := decodeHex(str); ok {
|
||||||
|
log.Print(hexStr)
|
||||||
|
value = hexStr
|
||||||
|
} else {
|
||||||
|
value = str
|
||||||
|
}
|
||||||
|
resultType = targetType
|
||||||
|
return
|
||||||
|
|
||||||
|
case types.BINARY:
|
||||||
|
var binary strings.Builder
|
||||||
|
for _, char := range str {
|
||||||
|
binary.WriteString(fmt.Sprintf("%08b", int(char)))
|
||||||
|
}
|
||||||
|
value = binary.String()
|
||||||
|
resultType = targetType
|
||||||
|
return
|
||||||
|
|
||||||
|
case types.GZIP, types.GZIP_JSON:
|
||||||
|
if gzipStr, ok := decodeGZip(str); ok {
|
||||||
|
if targetType == types.BASE64_JSON {
|
||||||
|
value, _ = decodeJson(gzipStr)
|
||||||
|
} else {
|
||||||
|
value = gzipStr
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = str
|
||||||
|
}
|
||||||
|
resultType = targetType
|
||||||
|
return
|
||||||
|
|
||||||
|
case types.DEFLATE:
|
||||||
|
value, _ = decodeDeflate(str)
|
||||||
|
resultType = targetType
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// type isn't specified or unknown, try to automatically detect and return converted value
|
||||||
|
return autoToType(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt automatic convert to possible types
|
||||||
|
// if no conversion is possible, it will return the origin string value and "plain text" type
|
||||||
|
func autoToType(str string) (value, resultType string) {
|
||||||
|
if len(str) > 0 {
|
||||||
|
var ok bool
|
||||||
|
if value, ok = decodeJson(str); ok {
|
||||||
|
resultType = types.JSON
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok = decodeBase64(str); ok {
|
||||||
|
if value, ok = decodeJson(value); ok {
|
||||||
|
resultType = types.BASE64_JSON
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultType = types.BASE64_TEXT
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok = decodeGZip(str); ok {
|
||||||
|
resultType = types.GZIP
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok = decodeDeflate(str); ok {
|
||||||
|
resultType = types.DEFLATE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value = str
|
||||||
|
resultType = types.PLAIN_TEXT
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeJson(str string) (string, bool) {
|
||||||
|
var data any
|
||||||
|
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
|
||||||
|
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
|
||||||
|
if err := json.Unmarshal([]byte(str), &data); err == nil {
|
||||||
|
var jsonByte []byte
|
||||||
|
if jsonByte, err = json.MarshalIndent(data, "", " "); err == nil {
|
||||||
|
return string(jsonByte), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBase64(str string) (string, bool) {
|
||||||
|
if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil {
|
||||||
|
return string(decodedStr), true
|
||||||
|
}
|
||||||
|
return str, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeHex(str string) (string, bool) {
|
||||||
|
encodeStr := hex.EncodeToString([]byte(str))
|
||||||
|
var resultStr strings.Builder
|
||||||
|
for i := 0; i < len(encodeStr); i += 2 {
|
||||||
|
resultStr.WriteString("\\x")
|
||||||
|
resultStr.WriteString(encodeStr[i : i+2])
|
||||||
|
}
|
||||||
|
return resultStr.String(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeGZip(str string) (string, bool) {
|
||||||
|
if reader, err := gzip.NewReader(strings.NewReader(str)); err == nil {
|
||||||
|
defer reader.Close()
|
||||||
|
var decompressed []byte
|
||||||
|
if decompressed, err = io.ReadAll(reader); err == nil {
|
||||||
|
return string(decompressed), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeDeflate(str string) (string, bool) {
|
||||||
|
reader := flate.NewReader(strings.NewReader(str))
|
||||||
|
defer reader.Close()
|
||||||
|
if decompressed, err := io.ReadAll(reader); err == nil {
|
||||||
|
return string(decompressed), true
|
||||||
|
}
|
||||||
|
return str, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveAs(str, targetType string) (value string, err error) {
|
||||||
|
switch targetType {
|
||||||
|
case types.PLAIN_TEXT:
|
||||||
|
return str, nil
|
||||||
|
|
||||||
|
case types.BASE64_TEXT:
|
||||||
|
base64Str, _ := encodeBase64(str)
|
||||||
|
return base64Str, nil
|
||||||
|
|
||||||
|
case types.JSON, types.BASE64_JSON, types.GZIP_JSON:
|
||||||
|
if jsonStr, ok := encodeJson(str); ok {
|
||||||
|
if targetType == types.BASE64_JSON {
|
||||||
|
base64Str, _ := encodeBase64(jsonStr)
|
||||||
|
return base64Str, nil
|
||||||
|
} else {
|
||||||
|
return jsonStr, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return str, errors.New("invalid json")
|
||||||
|
}
|
||||||
|
|
||||||
|
case types.GZIP:
|
||||||
|
if gzipStr, ok := encodeGZip(str); ok {
|
||||||
|
return gzipStr, nil
|
||||||
|
} else {
|
||||||
|
return str, errors.New("fail to build gzip data")
|
||||||
|
}
|
||||||
|
|
||||||
|
case types.DEFLATE:
|
||||||
|
if deflateStr, ok := encodeDeflate(str); ok {
|
||||||
|
return deflateStr, nil
|
||||||
|
} else {
|
||||||
|
return str, errors.New("fail to build deflate data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str, errors.New("fail to save with unknown error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeJson(str string) (string, bool) {
|
||||||
|
var data any
|
||||||
|
if (strings.HasPrefix(str, "{") && strings.HasSuffix(str, "}")) ||
|
||||||
|
(strings.HasPrefix(str, "[") && strings.HasSuffix(str, "]")) {
|
||||||
|
if err := json.Unmarshal([]byte(str), &data); err == nil {
|
||||||
|
var jsonByte []byte
|
||||||
|
if jsonByte, err = json.Marshal(data); err == nil {
|
||||||
|
return string(jsonByte), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeBase64(str string) (string, bool) {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(str)), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeGZip(str string) (string, bool) {
|
||||||
|
var compress = func(b []byte) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := gzip.NewWriter(&buf)
|
||||||
|
if _, err := writer.Write([]byte(str)); err != nil {
|
||||||
|
writer.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
writer.Close()
|
||||||
|
return string(buf.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if gzipStr, err := compress([]byte(str)); err == nil {
|
||||||
|
return gzipStr, true
|
||||||
|
}
|
||||||
|
return str, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeDeflate(str string) (string, bool) {
|
||||||
|
var compress = func(b []byte) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer, err := flate.NewWriter(&buf, flate.DefaultCompression)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(str)); err != nil {
|
||||||
|
writer.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
writer.Close()
|
||||||
|
return string(buf.Bytes()), nil
|
||||||
|
}
|
||||||
|
if deflateStr, err := compress([]byte(str)); err == nil {
|
||||||
|
return deflateStr, true
|
||||||
|
}
|
||||||
|
return str, false
|
||||||
|
}
|
|
@ -98,6 +98,7 @@ const tabContent = computed(() => {
|
||||||
ttl: tab.ttl,
|
ttl: tab.ttl,
|
||||||
value: tab.value,
|
value: tab.value,
|
||||||
size: tab.size || 0,
|
size: tab.size || 0,
|
||||||
|
viewAs: tab.viewAs,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -122,7 +123,7 @@ const onReloadKey = async () => {
|
||||||
if (tab == null || isEmpty(tab.key)) {
|
if (tab == null || isEmpty(tab.key)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
await connectionStore.loadKeyValue(tab.name, tab.db, tab.key)
|
await connectionStore.loadKeyValue(tab.name, tab.db, tab.key, tab.viewAs)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -153,7 +154,8 @@ const onReloadKey = async () => {
|
||||||
:name="tabContent.name"
|
:name="tabContent.name"
|
||||||
:ttl="tabContent.ttl"
|
:ttl="tabContent.ttl"
|
||||||
:value="tabContent.value"
|
:value="tabContent.value"
|
||||||
:size="tabContent.size" />
|
:size="tabContent.size"
|
||||||
|
:view-as="tabContent.viewAs" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,10 @@ import { useThemeVars } from 'naive-ui'
|
||||||
import { types } from '@/consts/value_view_type.js'
|
import { types } from '@/consts/value_view_type.js'
|
||||||
import Close from '@/components/icons/Close.vue'
|
import Close from '@/components/icons/Close.vue'
|
||||||
import Edit from '@/components/icons/Edit.vue'
|
import Edit from '@/components/icons/Edit.vue'
|
||||||
import { IsJson } from '@/utils/check_string_format.js'
|
|
||||||
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { map, toLower } from 'lodash'
|
import { map, toLower } from 'lodash'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
import useConnectionStore from 'stores/connections.js'
|
||||||
import { fromBase64, fromBase64Json, toBinary, toHex, toJsonText } from '@/utils/string_convert.js'
|
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -28,6 +26,10 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
value: String,
|
value: String,
|
||||||
size: Number,
|
size: Number,
|
||||||
|
viewAs: {
|
||||||
|
type: String,
|
||||||
|
default: types.PLAIN_TEXT,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const viewOption = computed(() =>
|
const viewOption = computed(() =>
|
||||||
|
@ -38,15 +40,15 @@ const viewOption = computed(() =>
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const viewAs = ref(types.PLAIN_TEXT)
|
// const viewAs = ref(types.PLAIN_TEXT)
|
||||||
|
|
||||||
const autoDetectFormat = () => {
|
const autoDetectFormat = () => {
|
||||||
// auto check format when loaded
|
// auto check format when loaded
|
||||||
if (IsJson(props.value)) {
|
// if (IsJson(props.value)) {
|
||||||
viewAs.value = types.JSON
|
// viewAs.value = types.JSON
|
||||||
} else {
|
// } else {
|
||||||
viewAs.value = types.PLAIN_TEXT
|
// viewAs.value = types.PLAIN_TEXT
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -60,44 +62,25 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
const keyType = redisTypes.STRING
|
const keyType = redisTypes.STRING
|
||||||
/**
|
|
||||||
* view value
|
|
||||||
* @type {ComputedRef<string>}
|
|
||||||
*/
|
|
||||||
const viewValue = computed(() => {
|
|
||||||
switch (viewAs.value) {
|
|
||||||
case types.PLAIN_TEXT:
|
|
||||||
return props.value
|
|
||||||
case types.JSON:
|
|
||||||
return toJsonText(props.value)
|
|
||||||
case types.BASE64_TO_TEXT:
|
|
||||||
return fromBase64(props.value)
|
|
||||||
case types.BASE64_TO_JSON:
|
|
||||||
return fromBase64Json(props.value)
|
|
||||||
case types.HEX:
|
|
||||||
return toHex(props.value)
|
|
||||||
case types.BINARY:
|
|
||||||
return toBinary(props.value)
|
|
||||||
default:
|
|
||||||
return props.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const viewLanguage = computed(() => {
|
const viewLanguage = computed(() => {
|
||||||
switch (viewAs.value) {
|
switch (props.viewAs) {
|
||||||
case types.JSON:
|
case types.JSON:
|
||||||
case types.BASE64_TO_JSON:
|
case types.BASE64_JSON:
|
||||||
return 'json'
|
return 'json'
|
||||||
default:
|
default:
|
||||||
return 'plaintext'
|
return 'plaintext'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onViewTypeUpdate = (viewType) => {
|
||||||
|
connectionStore.loadKeyValue(props.name, props.db, props.keyPath, viewType)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy value
|
* Copy value
|
||||||
*/
|
*/
|
||||||
const onCopyValue = () => {
|
const onCopyValue = () => {
|
||||||
ClipboardSetText(viewValue.value)
|
ClipboardSetText(props.value)
|
||||||
.then((succ) => {
|
.then((succ) => {
|
||||||
if (succ) {
|
if (succ) {
|
||||||
$message.success(i18n.t('dialogue.copy_succ'))
|
$message.success(i18n.t('dialogue.copy_succ'))
|
||||||
|
@ -111,7 +94,7 @@ const onCopyValue = () => {
|
||||||
const editValue = ref('')
|
const editValue = ref('')
|
||||||
const inEdit = ref(false)
|
const inEdit = ref(false)
|
||||||
const onEditValue = () => {
|
const onEditValue = () => {
|
||||||
editValue.value = viewValue.value
|
editValue.value = props.value
|
||||||
inEdit.value = true
|
inEdit.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +117,7 @@ const onSaveValue = async () => {
|
||||||
toLower(keyType),
|
toLower(keyType),
|
||||||
editValue.value,
|
editValue.value,
|
||||||
-1,
|
-1,
|
||||||
|
props.viewAs,
|
||||||
)
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
await connectionStore.loadKeyValue(props.name, props.db, props.keyPath)
|
await connectionStore.loadKeyValue(props.name, props.db, props.keyPath)
|
||||||
|
@ -155,7 +139,11 @@ const onSaveValue = async () => {
|
||||||
<content-toolbar :db="props.db" :key-path="keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
|
<content-toolbar :db="props.db" :key-path="keyPath" :key-type="keyType" :server="props.name" :ttl="ttl" />
|
||||||
<div class="tb2 flex-box-h">
|
<div class="tb2 flex-box-h">
|
||||||
<n-text>{{ $t('interface.view_as') }}</n-text>
|
<n-text>{{ $t('interface.view_as') }}</n-text>
|
||||||
<n-select v-model:value="viewAs" :options="viewOption" style="width: 200px" />
|
<n-select
|
||||||
|
:value="props.viewAs"
|
||||||
|
:options="viewOption"
|
||||||
|
style="width: 200px"
|
||||||
|
@update:value="onViewTypeUpdate" />
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<n-button-group v-if="!inEdit">
|
<n-button-group v-if="!inEdit">
|
||||||
<n-button :focusable="false" @click="onCopyValue">
|
<n-button :focusable="false" @click="onCopyValue">
|
||||||
|
@ -188,7 +176,7 @@ const onSaveValue = async () => {
|
||||||
</div>
|
</div>
|
||||||
<div class="value-wrapper flex-item-expand flex-box-v">
|
<div class="value-wrapper flex-item-expand flex-box-v">
|
||||||
<n-scrollbar v-if="!inEdit" class="flex-item-expand">
|
<n-scrollbar v-if="!inEdit" class="flex-item-expand">
|
||||||
<n-code :code="viewValue" :language="viewLanguage" show-line-numbers style="cursor: text" word-wrap />
|
<n-code :code="props.value" :language="viewLanguage" show-line-numbers style="cursor: text" word-wrap />
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
<n-input
|
<n-input
|
||||||
v-else
|
v-else
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, h, reactive, ref, watch } from 'vue'
|
import { computed, h, reactive, ref, watch } from 'vue'
|
||||||
import { types, typesColor } from '@/consts/support_redis_type.js'
|
import { types, typesColor } from '@/consts/support_redis_type.js'
|
||||||
|
import { types as viewTypes } from '@/consts/value_view_type.js'
|
||||||
import useDialog from 'stores/dialog'
|
import useDialog from 'stores/dialog'
|
||||||
import { isEmpty, keys, map } from 'lodash'
|
import { isEmpty, keys, map } from 'lodash'
|
||||||
import NewStringValue from '@/components/new_value/NewStringValue.vue'
|
import NewStringValue from '@/components/new_value/NewStringValue.vue'
|
||||||
|
@ -116,7 +117,15 @@ const onAdd = async () => {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = defaultValue[type]
|
value = defaultValue[type]
|
||||||
}
|
}
|
||||||
const { success, msg, nodeKey } = await connectionStore.setKey(server, db, key, type, value, ttl)
|
const { success, msg, nodeKey } = await connectionStore.setKey(
|
||||||
|
server,
|
||||||
|
db,
|
||||||
|
key,
|
||||||
|
type,
|
||||||
|
value,
|
||||||
|
ttl,
|
||||||
|
viewTypes.PLAIN_TEXT,
|
||||||
|
)
|
||||||
if (success) {
|
if (success) {
|
||||||
// select current key
|
// select current key
|
||||||
tabStore.setSelectedKeys(server, nodeKey)
|
tabStore.setSelectedKeys(server, nodeKey)
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
export const types = {
|
export const types = {
|
||||||
PLAIN_TEXT: 'Plain Text',
|
PLAIN_TEXT: 'Plain Text',
|
||||||
JSON: 'JSON',
|
JSON: 'JSON',
|
||||||
BASE64_TO_TEXT: 'Base64 To Text',
|
BASE64_TEXT: 'Base64 Text',
|
||||||
BASE64_TO_JSON: 'Base64 To JSON',
|
BASE64_JSON: 'Base64 JSON',
|
||||||
HEX: 'Hex',
|
HEX: 'Hex',
|
||||||
BINARY: 'Binary',
|
BINARY: 'Binary',
|
||||||
|
GZIP: 'GZip',
|
||||||
|
GZIP_JSON: 'GZip JSON',
|
||||||
|
DEFLATE: 'Deflate',
|
||||||
}
|
}
|
||||||
|
|
|
@ -525,14 +525,15 @@ const useConnectionStore = defineStore('connections', {
|
||||||
* @param {string} server
|
* @param {string} server
|
||||||
* @param {number} db
|
* @param {number} db
|
||||||
* @param {string} [key] when key is null or blank, update tab to display normal content (blank content or server status)
|
* @param {string} [key] when key is null or blank, update tab to display normal content (blank content or server status)
|
||||||
|
* @param {string} [viewType]
|
||||||
*/
|
*/
|
||||||
async loadKeyValue(server, db, key) {
|
async loadKeyValue(server, db, key, viewType) {
|
||||||
try {
|
try {
|
||||||
const tab = useTabStore()
|
const tab = useTabStore()
|
||||||
if (!isEmpty(key)) {
|
if (!isEmpty(key)) {
|
||||||
const { data, success, msg } = await GetKeyValue(server, db, key)
|
const { data, success, msg } = await GetKeyValue(server, db, key, viewType)
|
||||||
if (success) {
|
if (success) {
|
||||||
const { type, ttl, value, size } = data
|
const { type, ttl, value, size, viewAs } = data
|
||||||
tab.upsertTab({
|
tab.upsertTab({
|
||||||
server,
|
server,
|
||||||
db,
|
db,
|
||||||
|
@ -541,11 +542,16 @@ const useConnectionStore = defineStore('connections', {
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
size,
|
size,
|
||||||
|
viewAs,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
if (!isEmpty(msg)) {
|
||||||
|
$message.error('load key 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)
|
||||||
|
// TODO: show key not found page?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -852,11 +858,12 @@ const useConnectionStore = defineStore('connections', {
|
||||||
* @param {string} keyType
|
* @param {string} keyType
|
||||||
* @param {any} value
|
* @param {any} value
|
||||||
* @param {number} ttl
|
* @param {number} ttl
|
||||||
|
* @param {string} [viewAs]
|
||||||
* @returns {Promise<{[msg]: string, success: boolean, [nodeKey]: {string}}>}
|
* @returns {Promise<{[msg]: string, success: boolean, [nodeKey]: {string}}>}
|
||||||
*/
|
*/
|
||||||
async setKey(connName, db, key, keyType, value, ttl) {
|
async setKey(connName, db, key, keyType, value, ttl, viewAs) {
|
||||||
try {
|
try {
|
||||||
const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl)
|
const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl, viewAs)
|
||||||
if (success) {
|
if (success) {
|
||||||
// update tree view data
|
// update tree view data
|
||||||
const { newKey = 0 } = this._addKeyNodes(connName, db, [key], true)
|
const { newKey = 0 } = this._addKeyNodes(connName, db, [key], true)
|
||||||
|
|
|
@ -38,7 +38,7 @@ const usePreferencesStore = defineStore('preferences', {
|
||||||
},
|
},
|
||||||
general: {
|
general: {
|
||||||
theme: 'auto',
|
theme: 'auto',
|
||||||
language: 'en',
|
language: 'auto',
|
||||||
font: '',
|
font: '',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
useSysProxy: false,
|
useSysProxy: false,
|
||||||
|
|
|
@ -85,8 +85,9 @@ const useTabStore = defineStore('tab', {
|
||||||
* @param {string} [key]
|
* @param {string} [key]
|
||||||
* @param {number} [size]
|
* @param {number} [size]
|
||||||
* @param {*} [value]
|
* @param {*} [value]
|
||||||
|
* @param {string} [viewAs]
|
||||||
*/
|
*/
|
||||||
upsertTab({ server, db, type, ttl, key, size, value }) {
|
upsertTab({ server, db, type, ttl, key, size, value, viewAs }) {
|
||||||
let tabIndex = findIndex(this.tabList, { name: server })
|
let tabIndex = findIndex(this.tabList, { name: server })
|
||||||
if (tabIndex === -1) {
|
if (tabIndex === -1) {
|
||||||
this.tabList.push({
|
this.tabList.push({
|
||||||
|
@ -98,6 +99,7 @@ const useTabStore = defineStore('tab', {
|
||||||
key,
|
key,
|
||||||
size,
|
size,
|
||||||
value,
|
value,
|
||||||
|
viewAs,
|
||||||
})
|
})
|
||||||
tabIndex = this.tabList.length - 1
|
tabIndex = this.tabList.length - 1
|
||||||
}
|
}
|
||||||
|
@ -112,6 +114,7 @@ const useTabStore = defineStore('tab', {
|
||||||
tab.key = key
|
tab.key = key
|
||||||
tab.size = size
|
tab.size = size
|
||||||
tab.value = value
|
tab.value = value
|
||||||
|
tab.viewAs = viewAs
|
||||||
this._setActivatedIndex(tabIndex, true)
|
this._setActivatedIndex(tabIndex, true)
|
||||||
// this.activatedTab = tab.name
|
// this.activatedTab = tab.name
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { endsWith, isEmpty, size, startsWith } from 'lodash'
|
|
||||||
|
|
||||||
export const IsRedisKey = (str, separator) => {
|
|
||||||
if (isEmpty(separator)) {
|
|
||||||
separator = ':'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check string is json
|
|
||||||
* @param str
|
|
||||||
* @returns {boolean}
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
export const IsJson = (str) => {
|
|
||||||
if (size(str) >= 2) {
|
|
||||||
if (startsWith(str, '{') && endsWith(str, '}')) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (startsWith(str, '[') && endsWith(str, ']')) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
import { map, padStart } from 'lodash'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert string to json
|
|
||||||
* @param str
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export const toJsonText = (str) => {
|
|
||||||
try {
|
|
||||||
const jsonObj = JSON.parse(str)
|
|
||||||
return JSON.stringify(jsonObj, null, 2)
|
|
||||||
} catch (e) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert string from base64
|
|
||||||
* @param str
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export const fromBase64 = (str) => {
|
|
||||||
try {
|
|
||||||
return atob(str)
|
|
||||||
} catch (e) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert string from base64 to json
|
|
||||||
* @param str
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export const fromBase64Json = (str) => {
|
|
||||||
try {
|
|
||||||
const text = atob(str)
|
|
||||||
const jsonObj = JSON.parse(text)
|
|
||||||
return JSON.stringify(jsonObj, null, 2)
|
|
||||||
} catch (e) {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert string to hex string
|
|
||||||
* @param str
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export const toHex = (str) => {
|
|
||||||
const hexArr = map(str, (char) => {
|
|
||||||
const charCode = char.charCodeAt(0)
|
|
||||||
return charCode.toString(16)
|
|
||||||
})
|
|
||||||
return hexArr.join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert string to binary string
|
|
||||||
* @param str
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export const toBinary = (str) => {
|
|
||||||
const codeUnits = map(str, (char) => {
|
|
||||||
let code = char.charCodeAt(0).toString(2)
|
|
||||||
code = padStart(code, 8, '0')
|
|
||||||
return code
|
|
||||||
})
|
|
||||||
return codeUnits.join(' ')
|
|
||||||
}
|
|
10
main.go
10
main.go
|
@ -57,6 +57,16 @@ func main() {
|
||||||
app.startup(ctx)
|
app.startup(ctx)
|
||||||
connSvc.Start(ctx)
|
connSvc.Start(ctx)
|
||||||
},
|
},
|
||||||
|
//OnBeforeClose: func(ctx context.Context) (prevent bool) {
|
||||||
|
// // save current window size
|
||||||
|
// width, height := runtime2.WindowGetSize(ctx)
|
||||||
|
// if width > 0 && height > 0 {
|
||||||
|
// if w, h := prefSvc.GetWindowSize(); w != width || h != height {
|
||||||
|
// prefSvc.SaveWindowSize(width, height)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false
|
||||||
|
//},
|
||||||
OnShutdown: func(ctx context.Context) {
|
OnShutdown: func(ctx context.Context) {
|
||||||
// save current window size
|
// save current window size
|
||||||
width, height := runtime2.WindowGetSize(ctx)
|
width, height := runtime2.WindowGetSize(ctx)
|
||||||
|
|
Loading…
Reference in New Issue