mirror of
				https://github.com/tiny-craft/tiny-rdm.git
				synced 2025-11-01 02:52:33 +08:00 
			
		
		
		
	feat: split "view as" to "view type" and "decode type" #60
feat: add footer to the value content page
This commit is contained in:
		| @@ -791,7 +791,7 @@ func (c *connectionService) LoadAllKeys(connName string, db int, match, keyType | |||||||
| } | } | ||||||
|  |  | ||||||
| // GetKeyValue get value by key | // GetKeyValue get value by key | ||||||
| func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs string) (resp types.JSResp) { | func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs, decodeType string) (resp types.JSResp) { | ||||||
| 	item, err := c.getRedisClient(connName, db) | 	item, err := c.getRedisClient(connName, db) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		resp.Msg = err.Error() | 		resp.Msg = err.Error() | ||||||
| @@ -831,7 +831,7 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s | |||||||
| 	case "string": | 	case "string": | ||||||
| 		var str string | 		var str string | ||||||
| 		str, err = client.Get(ctx, key).Result() | 		str, err = client.Get(ctx, key).Result() | ||||||
| 		value, viewAs = strutil.ConvertTo(str, viewAs) | 		value, decodeType, viewAs = strutil.ConvertTo(str, decodeType, viewAs) | ||||||
| 		size, _ = client.StrLen(ctx, key).Result() | 		size, _ = client.StrLen(ctx, key).Result() | ||||||
| 	case "list": | 	case "list": | ||||||
| 		value, err = client.LRange(ctx, key, 0, -1).Result() | 		value, err = client.LRange(ctx, key, 0, -1).Result() | ||||||
| @@ -928,13 +928,14 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s | |||||||
| 		"value":  value, | 		"value":  value, | ||||||
| 		"size":   size, | 		"size":   size, | ||||||
| 		"viewAs": viewAs, | 		"viewAs": viewAs, | ||||||
|  | 		"decode": decodeType, | ||||||
| 	} | 	} | ||||||
| 	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, k any, keyType string, value any, ttl int64, viewAs string) (resp types.JSResp) { | func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType string, value any, ttl int64, viewAs, decode string) (resp types.JSResp) { | ||||||
| 	item, err := c.getRedisClient(connName, db) | 	item, err := c.getRedisClient(connName, db) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		resp.Msg = err.Error() | 		resp.Msg = err.Error() | ||||||
| @@ -958,7 +959,7 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType | |||||||
| 			return | 			return | ||||||
| 		} else { | 		} else { | ||||||
| 			var saveStr string | 			var saveStr string | ||||||
| 			if saveStr, err = strutil.SaveAs(str, viewAs); err != nil { | 			if saveStr, err = strutil.SaveAs(str, viewAs, decode); err != nil { | ||||||
| 				resp.Msg = fmt.Sprintf(`save to "%s" type fail: %s`, viewAs, err.Error()) | 				resp.Msg = fmt.Sprintf(`save to "%s" type fail: %s`, viewAs, err.Error()) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -1,14 +1,12 @@ | |||||||
| package types | package types | ||||||
|  |  | ||||||
| const PLAIN_TEXT = "Plain Text" | const VIEWAS_PLAIN_TEXT = "Plain Text" | ||||||
| const JSON = "JSON" | const VIEWAS_JSON = "JSON" | ||||||
| const BASE64_TEXT = "Base64 Text" | const VIEWAS_HEX = "Hex" | ||||||
| const BASE64_JSON = "Base64 JSON" | const VIEWAS_BINARY = "Binary" | ||||||
| const HEX = "Hex" |  | ||||||
| const BINARY = "Binary" | const DECODE_NONE = "None" | ||||||
| const GZIP = "GZip" | const DECODE_BASE64 = "Base64" | ||||||
| const GZIP_JSON = "GZip JSON" | const DECODE_GZIP = "GZip" | ||||||
| const DEFLATE = "Deflate" | const DECODE_DEFLATE = "Deflate" | ||||||
| const DEFLATE_JSON = "Deflate JSON" | const DECODE_BROTLI = "Brotli" | ||||||
| const BROTLI = "Brotli" |  | ||||||
| const BROTLI_JSON = "Brotli JSON" |  | ||||||
|   | |||||||
| @@ -18,160 +18,171 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| // ConvertTo convert string to specified type | // ConvertTo convert string to specified type | ||||||
| // @param targetType  empty string indicates automatic detection of the string type | // @param decodeType empty string indicates automatic detection | ||||||
| func ConvertTo(str, targetType string) (value, resultType string) { | // @param formatType empty string indicates automatic detection | ||||||
|  | func ConvertTo(str, decodeType, formatType string) (value, resultDecode, resultFormat string) { | ||||||
| 	if len(str) <= 0 { | 	if len(str) <= 0 { | ||||||
| 		// empty content | 		// empty content | ||||||
| 		if len(targetType) <= 0 { | 		if len(formatType) <= 0 { | ||||||
| 			resultType = types.PLAIN_TEXT | 			resultFormat = types.VIEWAS_PLAIN_TEXT | ||||||
| 		} else { | 		} else { | ||||||
| 			resultType = targetType | 			resultFormat = formatType | ||||||
|  | 		} | ||||||
|  | 		if len(decodeType) <= 0 { | ||||||
|  | 			resultDecode = types.DECODE_NONE | ||||||
|  | 		} else { | ||||||
|  | 			resultDecode = decodeType | ||||||
| 		} | 		} | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch targetType { | 	// decode first | ||||||
| 	case types.PLAIN_TEXT: | 	value, resultDecode = decodeWith(str, decodeType) | ||||||
|  | 	// then format content | ||||||
|  | 	value, resultFormat = viewAs(value, formatType) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func decodeWith(str, decodeType string) (value, resultDecode string) { | ||||||
|  | 	if len(decodeType) > 0 { | ||||||
|  | 		switch decodeType { | ||||||
|  | 		case types.DECODE_NONE: | ||||||
| 			value = str | 			value = str | ||||||
| 		resultType = targetType | 			resultDecode = decodeType | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
| 	case types.JSON: | 		case types.DECODE_BASE64: | ||||||
| 		value, _ = decodeJson(str) |  | ||||||
| 		resultType = targetType |  | ||||||
| 		return |  | ||||||
|  |  | ||||||
| 	case types.BASE64_TEXT, types.BASE64_JSON: |  | ||||||
| 			if base64Str, ok := decodeBase64(str); ok { | 			if base64Str, ok := decodeBase64(str); ok { | ||||||
| 			if targetType == types.BASE64_JSON { |  | ||||||
| 				value, _ = decodeJson(base64Str) |  | ||||||
| 			} else { |  | ||||||
| 				value = base64Str | 				value = base64Str | ||||||
| 			} |  | ||||||
| 			} else { | 			} else { | ||||||
| 				value = str | 				value = str | ||||||
| 			} | 			} | ||||||
| 		resultType = targetType | 			resultDecode = decodeType | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
| 	case types.HEX: | 		case types.DECODE_GZIP: | ||||||
|  | 			if gzipStr, ok := decodeGZip(str); ok { | ||||||
|  | 				value = gzipStr | ||||||
|  | 			} else { | ||||||
|  | 				value = str | ||||||
|  | 			} | ||||||
|  | 			resultDecode = decodeType | ||||||
|  | 			return | ||||||
|  |  | ||||||
|  | 		case types.DECODE_DEFLATE: | ||||||
|  | 			if gzipStr, ok := decodeDeflate(str); ok { | ||||||
|  | 				value = gzipStr | ||||||
|  | 			} else { | ||||||
|  | 				value = str | ||||||
|  | 			} | ||||||
|  | 			resultDecode = decodeType | ||||||
|  | 			return | ||||||
|  |  | ||||||
|  | 		case types.DECODE_BROTLI: | ||||||
|  | 			if gzipStr, ok := decodeBrotli(str); ok { | ||||||
|  | 				value = gzipStr | ||||||
|  | 			} else { | ||||||
|  | 				value = str | ||||||
|  | 			} | ||||||
|  | 			resultDecode = decodeType | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return autoDecode(str) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // attempt try possible decode method | ||||||
|  | // if no decode is possible, it will return the origin string value and "none" decode type | ||||||
|  | func autoDecode(str string) (value, resultDecode string) { | ||||||
|  | 	if len(str) > 0 { | ||||||
|  | 		var ok bool | ||||||
|  | 		if value, ok = decodeBase64(str); ok { | ||||||
|  | 			resultDecode = types.DECODE_BASE64 | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if value, ok = decodeGZip(str); ok { | ||||||
|  | 			resultDecode = types.DECODE_GZIP | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if value, ok = decodeDeflate(str); ok { | ||||||
|  | 			resultDecode = types.DECODE_DEFLATE | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if value, ok = decodeBrotli(str); ok { | ||||||
|  | 			resultDecode = types.DECODE_BROTLI | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	value = str | ||||||
|  | 	resultDecode = types.DECODE_NONE | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func viewAs(str, formatType string) (value, resultFormat string) { | ||||||
|  | 	if len(formatType) > 0 { | ||||||
|  | 		switch formatType { | ||||||
|  | 		case types.VIEWAS_PLAIN_TEXT: | ||||||
|  | 			value = str | ||||||
|  | 			resultFormat = formatType | ||||||
|  | 			return | ||||||
|  |  | ||||||
|  | 		case types.VIEWAS_JSON: | ||||||
|  | 			if jsonStr, ok := decodeJson(str); ok { | ||||||
|  | 				value = jsonStr | ||||||
|  | 			} else { | ||||||
|  | 				value = str | ||||||
|  | 			} | ||||||
|  | 			resultFormat = formatType | ||||||
|  | 			return | ||||||
|  |  | ||||||
|  | 		case types.VIEWAS_HEX: | ||||||
| 			if hexStr, ok := decodeToHex(str); ok { | 			if hexStr, ok := decodeToHex(str); ok { | ||||||
| 				value = hexStr | 				value = hexStr | ||||||
| 			} else { | 			} else { | ||||||
| 				value = str | 				value = str | ||||||
| 			} | 			} | ||||||
| 		resultType = targetType | 			resultFormat = formatType | ||||||
| 			return | 			return | ||||||
|  |  | ||||||
| 	case types.BINARY: | 		case types.VIEWAS_BINARY: | ||||||
| 			if binStr, ok := decodeBinary(str); ok { | 			if binStr, ok := decodeBinary(str); ok { | ||||||
| 				value = binStr | 				value = binStr | ||||||
| 			} else { | 			} else { | ||||||
| 				value = str | 				value = str | ||||||
| 			} | 			} | ||||||
| 		resultType = targetType | 			resultFormat = formatType | ||||||
| 		return |  | ||||||
|  |  | ||||||
| 	case types.GZIP, types.GZIP_JSON: |  | ||||||
| 		if gzipStr, ok := decodeGZip(str); ok { |  | ||||||
| 			if targetType == types.GZIP_JSON { |  | ||||||
| 				value, _ = decodeJson(gzipStr) |  | ||||||
| 			} else { |  | ||||||
| 				value = gzipStr |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			value = str |  | ||||||
| 		} |  | ||||||
| 		resultType = targetType |  | ||||||
| 		return |  | ||||||
|  |  | ||||||
| 	case types.DEFLATE, types.DEFLATE_JSON: |  | ||||||
| 		if deflateStr, ok := decodeDeflate(str); ok { |  | ||||||
| 			if targetType == types.DEFLATE_JSON { |  | ||||||
| 				value, _ = decodeJson(deflateStr) |  | ||||||
| 			} else { |  | ||||||
| 				value = deflateStr |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			value = str |  | ||||||
| 		} |  | ||||||
| 		resultType = targetType |  | ||||||
| 		return |  | ||||||
|  |  | ||||||
| 	case types.BROTLI, types.BROTLI_JSON: |  | ||||||
| 		if brotliStr, ok := decodeBrotli(str); ok { |  | ||||||
| 			if targetType == types.BROTLI_JSON { |  | ||||||
| 				value, _ = decodeJson(brotliStr) |  | ||||||
| 			} else { |  | ||||||
| 				value = brotliStr |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			value = str |  | ||||||
| 		} |  | ||||||
| 		resultType = targetType |  | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// type isn't specified or unknown, try to automatically detect and return converted value | 	return autoViewAs(str) | ||||||
| 	return autoToType(str) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // attempt automatic convert to possible types | // attempt automatic convert to possible types | ||||||
| // if no conversion is possible, it will return the origin string value and "plain text" type | // if no conversion is possible, it will return the origin string value and "plain text" type | ||||||
| func autoToType(str string) (value, resultType string) { | func autoViewAs(str string) (value, resultFormat string) { | ||||||
| 	if len(str) > 0 { | 	if len(str) > 0 { | ||||||
| 		var ok bool | 		var ok bool | ||||||
| 		if value, ok = decodeJson(str); ok { | 		if value, ok = decodeJson(str); ok { | ||||||
| 			resultType = types.JSON | 			resultFormat = types.VIEWAS_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 { |  | ||||||
| 			if value, ok = decodeJson(value); ok { |  | ||||||
| 				resultType = types.GZIP_JSON |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			resultType = types.GZIP |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if value, ok = decodeDeflate(str); ok { |  | ||||||
| 			if value, ok = decodeJson(value); ok { |  | ||||||
| 				resultType = types.DEFLATE_JSON |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			resultType = types.DEFLATE |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if value, ok = decodeBrotli(str); ok { |  | ||||||
| 			if value, ok = decodeJson(value); ok { |  | ||||||
| 				resultType = types.BROTLI_JSON |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			resultType = types.BROTLI |  | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if containsBinary(str) { | 		if containsBinary(str) { | ||||||
| 			if value, ok = decodeToHex(str); ok { | 			if value, ok = decodeToHex(str); ok { | ||||||
| 				resultType = types.HEX | 				resultFormat = types.VIEWAS_HEX | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	value = str | 	value = str | ||||||
| 	resultType = types.PLAIN_TEXT | 	resultFormat = types.VIEWAS_PLAIN_TEXT | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -241,65 +252,65 @@ func decodeBrotli(str string) (string, bool) { | |||||||
| 	return str, false | 	return str, false | ||||||
| } | } | ||||||
|  |  | ||||||
| func SaveAs(str, targetType string) (value string, err error) { | func SaveAs(str, viewType, decodeType string) (value string, err error) { | ||||||
| 	switch targetType { | 	value = str | ||||||
| 	case types.PLAIN_TEXT: | 	switch viewType { | ||||||
| 		return str, nil | 	case types.VIEWAS_JSON: | ||||||
|  |  | ||||||
| 	case types.BASE64_TEXT: |  | ||||||
| 		base64Str, _ := encodeBase64(str) |  | ||||||
| 		return base64Str, nil |  | ||||||
|  |  | ||||||
| 	case types.HEX: |  | ||||||
| 		hexStr, _ := encodeHex(str) |  | ||||||
| 		return hexStr, nil |  | ||||||
|  |  | ||||||
| 	case types.BINARY: |  | ||||||
| 		binStr, _ := encodeBinary(str) |  | ||||||
| 		return binStr, nil |  | ||||||
|  |  | ||||||
| 	case types.JSON, types.BASE64_JSON, types.GZIP_JSON, types.DEFLATE_JSON, types.BROTLI_JSON: |  | ||||||
| 		if jsonStr, ok := encodeJson(str); ok { | 		if jsonStr, ok := encodeJson(str); ok { | ||||||
| 			switch targetType { | 			value = jsonStr | ||||||
| 			case types.BASE64_JSON: |  | ||||||
| 				base64Str, _ := encodeBase64(jsonStr) |  | ||||||
| 				return base64Str, nil |  | ||||||
| 			case types.GZIP_JSON: |  | ||||||
| 				gzipStr, _ := encodeGZip(jsonStr) |  | ||||||
| 				return gzipStr, nil |  | ||||||
| 			case types.DEFLATE_JSON: |  | ||||||
| 				deflateStr, _ := encodeDeflate(jsonStr) |  | ||||||
| 				return deflateStr, nil |  | ||||||
| 			case types.BROTLI_JSON: |  | ||||||
| 				brotliStr, _ := encodeBrotli(jsonStr) |  | ||||||
| 				return brotliStr, nil |  | ||||||
| 			default: |  | ||||||
| 				return jsonStr, nil |  | ||||||
| 			} |  | ||||||
| 		} else { | 		} else { | ||||||
| 			return str, errors.New("invalid json") | 			err = errors.New("invalid json data") | ||||||
|  | 			return | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 	case types.GZIP: | 	case types.VIEWAS_HEX: | ||||||
|  | 		if hexStr, ok := encodeHex(str); ok { | ||||||
|  | 			value = hexStr | ||||||
|  | 		} else { | ||||||
|  | 			err = errors.New("invalid hex data") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	case types.VIEWAS_BINARY: | ||||||
|  | 		if binStr, ok := encodeBinary(str); ok { | ||||||
|  | 			value = binStr | ||||||
|  | 		} else { | ||||||
|  | 			err = errors.New("invalid binary data") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch decodeType { | ||||||
|  | 	case types.DECODE_NONE: | ||||||
|  | 		return | ||||||
|  |  | ||||||
|  | 	case types.DECODE_BASE64: | ||||||
|  | 		value, _ = encodeBase64(value) | ||||||
|  | 		return | ||||||
|  |  | ||||||
|  | 	case types.DECODE_GZIP: | ||||||
| 		if gzipStr, ok := encodeGZip(str); ok { | 		if gzipStr, ok := encodeGZip(str); ok { | ||||||
| 			return gzipStr, nil | 			value = gzipStr | ||||||
| 		} else { | 		} else { | ||||||
| 			return str, errors.New("fail to build gzip data") | 			err = errors.New("fail to build gzip") | ||||||
| 		} | 		} | ||||||
|  | 		return | ||||||
|  |  | ||||||
| 	case types.DEFLATE: | 	case types.DECODE_DEFLATE: | ||||||
| 		if deflateStr, ok := encodeDeflate(str); ok { | 		if deflateStr, ok := encodeDeflate(str); ok { | ||||||
| 			return deflateStr, nil | 			value = deflateStr | ||||||
| 		} else { | 		} else { | ||||||
| 			return str, errors.New("fail to build deflate data") | 			err = errors.New("fail to build deflate") | ||||||
| 		} | 		} | ||||||
|  | 		return | ||||||
|  |  | ||||||
| 	case types.BROTLI: | 	case types.DECODE_BROTLI: | ||||||
| 		if brotliStr, ok := encodeBrotli(str); ok { | 		if brotliStr, ok := encodeBrotli(str); ok { | ||||||
| 			return brotliStr, nil | 			value = brotliStr | ||||||
| 		} else { | 		} else { | ||||||
| 			return str, errors.New("fail to build brotli data") | 			err = errors.New("fail to build brotli") | ||||||
| 		} | 		} | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 	return str, errors.New("fail to save with unknown error") | 	return str, errors.New("fail to save with unknown error") | ||||||
| } | } | ||||||
|   | |||||||
| @@ -107,8 +107,8 @@ onMounted(async () => { | |||||||
|     <!-- app content--> |     <!-- app content--> | ||||||
|     <n-spin |     <n-spin | ||||||
|         :show="props.loading" |         :show="props.loading" | ||||||
|         :theme-overrides="{ opacitySpinning: 0 }" |         :style="{ backgroundColor: themeVars.bodyColor }" | ||||||
|         :style="{ backgroundColor: themeVars.bodyColor }"> |         :theme-overrides="{ opacitySpinning: 0 }"> | ||||||
|         <div id="app-content-wrapper" :style="wrapperStyle" class="flex-box-v"> |         <div id="app-content-wrapper" :style="wrapperStyle" class="flex-box-v"> | ||||||
|             <!-- title bar --> |             <!-- title bar --> | ||||||
|             <div |             <div | ||||||
|   | |||||||
| @@ -34,11 +34,11 @@ const hasTooltip = computed(() => { | |||||||
|     <n-tooltip v-if="hasTooltip" :show-arrow="false"> |     <n-tooltip v-if="hasTooltip" :show-arrow="false"> | ||||||
|         <template #trigger> |         <template #trigger> | ||||||
|             <n-button |             <n-button | ||||||
|  |                 :color="props.color" | ||||||
|                 :disabled="disabled" |                 :disabled="disabled" | ||||||
|                 :focusable="false" |                 :focusable="false" | ||||||
|                 :loading="loading" |                 :loading="loading" | ||||||
|                 :text="!border" |                 :text="!border" | ||||||
|                 :color="props.color" |  | ||||||
|                 @click.prevent="emit('click')"> |                 @click.prevent="emit('click')"> | ||||||
|                 <template #icon> |                 <template #icon> | ||||||
|                     <n-icon :color="props.color || 'currentColor'" :size="props.size"> |                     <n-icon :color="props.color || 'currentColor'" :size="props.size"> | ||||||
| @@ -51,11 +51,11 @@ const hasTooltip = computed(() => { | |||||||
|     </n-tooltip> |     </n-tooltip> | ||||||
|     <n-button |     <n-button | ||||||
|         v-else |         v-else | ||||||
|  |         :color="props.color" | ||||||
|         :disabled="disabled" |         :disabled="disabled" | ||||||
|         :focusable="false" |         :focusable="false" | ||||||
|         :loading="loading" |         :loading="loading" | ||||||
|         :text="!border" |         :text="!border" | ||||||
|         :color="props.color" |  | ||||||
|         @click.prevent="emit('click')"> |         @click.prevent="emit('click')"> | ||||||
|         <template #icon> |         <template #icon> | ||||||
|             <n-icon :color="props.color || 'currentColor'" :size="props.size"> |             <n-icon :color="props.color || 'currentColor'" :size="props.size"> | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ const tabContent = computed(() => { | |||||||
|         value: tab.value, |         value: tab.value, | ||||||
|         size: tab.size || 0, |         size: tab.size || 0, | ||||||
|         viewAs: tab.viewAs, |         viewAs: tab.viewAs, | ||||||
|  |         decode: tab.decode, | ||||||
|     } |     } | ||||||
| }) | }) | ||||||
|  |  | ||||||
| @@ -127,6 +128,7 @@ watch( | |||||||
|                 <content-value-wrapper |                 <content-value-wrapper | ||||||
|                     :blank="isBlankValue" |                     :blank="isBlankValue" | ||||||
|                     :db="tabContent.db" |                     :db="tabContent.db" | ||||||
|  |                     :decode="tabContent.decode" | ||||||
|                     :key-code="tabContent.keyCode" |                     :key-code="tabContent.keyCode" | ||||||
|                     :key-path="tabContent.keyPath" |                     :key-path="tabContent.keyPath" | ||||||
|                     :name="tabContent.name" |                     :name="tabContent.name" | ||||||
|   | |||||||
| @@ -128,7 +128,7 @@ const onDeleteKey = () => { | |||||||
|             </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="onRenameKey" /> | ||||||
|         </n-button-group> |         </n-button-group> | ||||||
|         <n-tooltip> |         <n-tooltip :show-arrow="false"> | ||||||
|             <template #trigger> |             <template #trigger> | ||||||
|                 <n-button :focusable="false" @click="onDeleteKey"> |                 <n-button :focusable="false" @click="onDeleteKey"> | ||||||
|                     <template #icon> |                     <template #icon> | ||||||
|   | |||||||
| @@ -1,17 +1,20 @@ | |||||||
| <script setup> | <script setup> | ||||||
| import { computed, onMounted, ref, watch } from 'vue' | import { computed, ref } from 'vue' | ||||||
| import { useI18n } from 'vue-i18n' | import { useI18n } from 'vue-i18n' | ||||||
| import ContentToolbar from './ContentToolbar.vue' | import ContentToolbar from './ContentToolbar.vue' | ||||||
| import Copy from '@/components/icons/Copy.vue' | import Copy from '@/components/icons/Copy.vue' | ||||||
| import Save from '@/components/icons/Save.vue' | import Save from '@/components/icons/Save.vue' | ||||||
| import { useThemeVars } from 'naive-ui' | import { useThemeVars } from 'naive-ui' | ||||||
| import { types } from '@/consts/value_view_type.js' | import { decodeTypes, formatTypes } 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 { 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 { isEmpty, map, toLower } from 'lodash' | import { isEmpty, toLower } from 'lodash' | ||||||
| import useConnectionStore from 'stores/connections.js' | import useConnectionStore from 'stores/connections.js' | ||||||
|  | import DropdownSelector from '@/components/content_value/DropdownSelector.vue' | ||||||
|  | import Code from '@/components/icons/Code.vue' | ||||||
|  | import Conversion from '@/components/icons/Conversion.vue' | ||||||
|  | import EditFile from '@/components/icons/EditFile.vue' | ||||||
|  |  | ||||||
| const i18n = useI18n() | const i18n = useI18n() | ||||||
| const themeVars = useThemeVars() | const themeVars = useThemeVars() | ||||||
| @@ -32,7 +35,11 @@ const props = defineProps({ | |||||||
|     size: Number, |     size: Number, | ||||||
|     viewAs: { |     viewAs: { | ||||||
|         type: String, |         type: String, | ||||||
|         default: types.PLAIN_TEXT, |         default: formatTypes.PLAIN_TEXT, | ||||||
|  |     }, | ||||||
|  |     decode: { | ||||||
|  |         type: String, | ||||||
|  |         default: decodeTypes.NONE, | ||||||
|     }, |     }, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| @@ -44,43 +51,20 @@ const keyName = computed(() => { | |||||||
|     return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath |     return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath | ||||||
| }) | }) | ||||||
|  |  | ||||||
| const viewOption = computed(() => | // const viewOption = computed(() => | ||||||
|     map(types, (t) => { | //     map(types, (t) => { | ||||||
|         return { | //         return { | ||||||
|             value: t, | //             value: t, | ||||||
|             label: t, | //             label: t, | ||||||
|         } | //             key: t, | ||||||
|     }), | //         } | ||||||
| ) | //     }), | ||||||
| // const viewAs = ref(types.PLAIN_TEXT) | // ) | ||||||
|  |  | ||||||
| const autoDetectFormat = () => { |  | ||||||
|     // auto check format when loaded |  | ||||||
|     // if (IsJson(props.value)) { |  | ||||||
|     //     viewAs.value = types.JSON |  | ||||||
|     // } else { |  | ||||||
|     //     viewAs.value = types.PLAIN_TEXT |  | ||||||
|     // } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| onMounted(() => { |  | ||||||
|     autoDetectFormat() |  | ||||||
| }) |  | ||||||
| watch( |  | ||||||
|     () => props.value, |  | ||||||
|     (value) => { |  | ||||||
|         autoDetectFormat() |  | ||||||
|     }, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const keyType = redisTypes.STRING | const keyType = redisTypes.STRING | ||||||
| const viewLanguage = computed(() => { | const viewLanguage = computed(() => { | ||||||
|     switch (props.viewAs) { |     switch (props.viewAs) { | ||||||
|         case types.JSON: |         case formatTypes.JSON: | ||||||
|         case types.BASE64_JSON: |  | ||||||
|         case types.GZIP_JSON: |  | ||||||
|         case types.DEFLATE_JSON: |  | ||||||
|         case types.BROTLI_JSON: |  | ||||||
|             return 'json' |             return 'json' | ||||||
|         default: |         default: | ||||||
|             return 'plaintext' |             return 'plaintext' | ||||||
| @@ -88,7 +72,11 @@ const viewLanguage = computed(() => { | |||||||
| }) | }) | ||||||
|  |  | ||||||
| const onViewTypeUpdate = (viewType) => { | const onViewTypeUpdate = (viewType) => { | ||||||
|     connectionStore.loadKeyValue(props.name, props.db, keyName.value, viewType) |     connectionStore.loadKeyValue(props.name, props.db, keyName.value, viewType, props.decode) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const onDecodeTypeUpdate = (decodeType) => { | ||||||
|  |     connectionStore.loadKeyValue(props.name, props.db, keyName.value, props.viewAs, decodeType) | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -133,6 +121,7 @@ const onSaveValue = async () => { | |||||||
|             editValue.value, |             editValue.value, | ||||||
|             -1, |             -1, | ||||||
|             props.viewAs, |             props.viewAs, | ||||||
|  |             props.decode, | ||||||
|         ) |         ) | ||||||
|         if (success) { |         if (success) { | ||||||
|             await connectionStore.loadKeyValue(props.name, props.db, keyName.value) |             await connectionStore.loadKeyValue(props.name, props.db, keyName.value) | ||||||
| @@ -157,15 +146,9 @@ const onSaveValue = async () => { | |||||||
|             :key-path="keyPath" |             :key-path="keyPath" | ||||||
|             :key-type="keyType" |             :key-type="keyType" | ||||||
|             :server="props.name" |             :server="props.name" | ||||||
|             :ttl="ttl" /> |             :ttl="ttl" | ||||||
|         <div class="tb2 flex-box-h"> |             class="value-item-part" /> | ||||||
|             <n-text>{{ $t('interface.view_as') }}</n-text> |         <div class="tb2 value-item-part flex-box-h"> | ||||||
|             <n-select |  | ||||||
|                 :options="viewOption" |  | ||||||
|                 :value="props.viewAs" |  | ||||||
|                 filterable |  | ||||||
|                 style="width: 160px" |  | ||||||
|                 @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"> | ||||||
| @@ -176,7 +159,7 @@ const onSaveValue = async () => { | |||||||
|                 </n-button> |                 </n-button> | ||||||
|                 <n-button :focusable="false" plain @click="onEditValue"> |                 <n-button :focusable="false" plain @click="onEditValue"> | ||||||
|                     <template #icon> |                     <template #icon> | ||||||
|                         <n-icon :component="Edit" size="18" /> |                         <n-icon :component="EditFile" size="18" /> | ||||||
|                     </template> |                     </template> | ||||||
|                     {{ $t('interface.edit_value') }} |                     {{ $t('interface.edit_value') }} | ||||||
|                 </n-button> |                 </n-button> | ||||||
| @@ -196,7 +179,7 @@ const onSaveValue = async () => { | |||||||
|                 </n-button> |                 </n-button> | ||||||
|             </n-button-group> |             </n-button-group> | ||||||
|         </div> |         </div> | ||||||
|         <div class="value-wrapper flex-item-expand flex-box-v"> |         <div class="value-wrapper value-item-part 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="props.value" :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> | ||||||
| @@ -208,6 +191,24 @@ const onSaveValue = async () => { | |||||||
|                 class="flex-item-expand" |                 class="flex-item-expand" | ||||||
|                 type="textarea" /> |                 type="textarea" /> | ||||||
|         </div> |         </div> | ||||||
|  |         <div class="value-footer flex-box-h"> | ||||||
|  |             <div class="flex-item-expand"></div> | ||||||
|  |             <dropdown-selector | ||||||
|  |                 :icon="Code" | ||||||
|  |                 :options="formatTypes" | ||||||
|  |                 :tooltip="$t('interface.view_as')" | ||||||
|  |                 :value="props.viewAs" | ||||||
|  |                 @update:value="onViewTypeUpdate" /> | ||||||
|  |  | ||||||
|  |             <n-divider vertical /> | ||||||
|  |  | ||||||
|  |             <dropdown-selector | ||||||
|  |                 :icon="Conversion" | ||||||
|  |                 :options="decodeTypes" | ||||||
|  |                 :tooltip="$t('interface.decode_with')" | ||||||
|  |                 :value="props.decode" | ||||||
|  |                 @update:value="onDecodeTypeUpdate" /> | ||||||
|  |         </div> | ||||||
|     </div> |     </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| @@ -217,4 +218,9 @@ const onSaveValue = async () => { | |||||||
|     border-top: v-bind('themeVars.borderColor') 1px solid; |     border-top: v-bind('themeVars.borderColor') 1px solid; | ||||||
|     padding: 5px; |     padding: 5px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .value-footer { | ||||||
|  |     border-top: v-bind('themeVars.borderColor') 1px solid; | ||||||
|  |     background-color: v-bind('themeVars.bodyColor'); | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <script setup> | <script setup> | ||||||
| import { types } from '@/consts/value_view_type.js' | 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' | ||||||
| @@ -31,7 +31,11 @@ const props = defineProps({ | |||||||
|     size: Number, |     size: Number, | ||||||
|     viewAs: { |     viewAs: { | ||||||
|         type: String, |         type: String, | ||||||
|         default: types.PLAIN_TEXT, |         default: formatTypes.PLAIN_TEXT, | ||||||
|  |     }, | ||||||
|  |     decode: { | ||||||
|  |         type: String, | ||||||
|  |         default: decodeTypes.NONE, | ||||||
|     }, |     }, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| @@ -63,6 +67,7 @@ const onReloadKey = async () => { | |||||||
|         <component |         <component | ||||||
|             :is="valueComponents[props.type]" |             :is="valueComponents[props.type]" | ||||||
|             :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" | ||||||
|             :name="props.name" |             :name="props.name" | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								frontend/src/components/content_value/DropdownSelector.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								frontend/src/components/content_value/DropdownSelector.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | <script setup> | ||||||
|  | import { computed, h, ref } from 'vue' | ||||||
|  | import { map } from 'lodash' | ||||||
|  | import { NIcon, NText } from 'naive-ui' | ||||||
|  |  | ||||||
|  | const props = defineProps({ | ||||||
|  |     value: { | ||||||
|  |         type: String, | ||||||
|  |         value: '', | ||||||
|  |     }, | ||||||
|  |     options: { | ||||||
|  |         type: Object, | ||||||
|  |         value: {}, | ||||||
|  |     }, | ||||||
|  |     tooltip: { | ||||||
|  |         type: String, | ||||||
|  |     }, | ||||||
|  |     icon: [String, Object], | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const emit = defineEmits(['update:value']) | ||||||
|  |  | ||||||
|  | const renderHeader = () => { | ||||||
|  |     return h('div', { class: 'type-selector-header' }, [h(NText, null, () => props.tooltip)]) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const renderLabel = (option) => { | ||||||
|  |     return h('div', { class: 'type-selector-item' }, option.label) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const dropdownOption = computed(() => { | ||||||
|  |     const options = [ | ||||||
|  |         { | ||||||
|  |             key: 'header', | ||||||
|  |             type: 'render', | ||||||
|  |             render: renderHeader, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             key: 'header-divider', | ||||||
|  |             type: 'divider', | ||||||
|  |         }, | ||||||
|  |     ] | ||||||
|  |     return [ | ||||||
|  |         ...options, | ||||||
|  |         ...map(props.options, (t) => { | ||||||
|  |             return { | ||||||
|  |                 key: t, | ||||||
|  |                 label: t, | ||||||
|  |             } | ||||||
|  |         }), | ||||||
|  |     ] | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const onDropdownSelect = (key) => { | ||||||
|  |     emit('update:value', key) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const buttonText = computed(() => { | ||||||
|  |     return props.value || get(dropdownOption.value, [1, 'label'], 'None') | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | const showDropdown = ref(false) | ||||||
|  | const onDropdownShow = (show) => { | ||||||
|  |     showDropdown.value = show === true | ||||||
|  | } | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <n-dropdown | ||||||
|  |         :options="dropdownOption" | ||||||
|  |         :render-label="renderLabel" | ||||||
|  |         :show-arrow="true" | ||||||
|  |         :title="props.tooltip" | ||||||
|  |         :value="props.value" | ||||||
|  |         trigger="click" | ||||||
|  |         @select="onDropdownSelect" | ||||||
|  |         @update:show="onDropdownShow"> | ||||||
|  |         <n-tooltip :disabled="showDropdown" :show-arrow="false"> | ||||||
|  |             {{ props.tooltip }} | ||||||
|  |             <template #trigger> | ||||||
|  |                 <n-button :focusable="false" quaternary> | ||||||
|  |                     <template #icon> | ||||||
|  |                         <n-icon> | ||||||
|  |                             <component :is="icon" /> | ||||||
|  |                         </n-icon> | ||||||
|  |                     </template> | ||||||
|  |                     {{ buttonText }} | ||||||
|  |                 </n-button> | ||||||
|  |             </template> | ||||||
|  |         </n-tooltip> | ||||||
|  |     </n-dropdown> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped></style> | ||||||
| @@ -1,7 +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 { decodeTypes, formatTypes } 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' | ||||||
| @@ -124,7 +124,8 @@ const onAdd = async () => { | |||||||
|             type, |             type, | ||||||
|             value, |             value, | ||||||
|             ttl, |             ttl, | ||||||
|             viewTypes.PLAIN_TEXT, |             formatTypes.PLAIN_TEXT, | ||||||
|  |             decodeTypes.NONE, | ||||||
|         ) |         ) | ||||||
|         if (success) { |         if (success) { | ||||||
|             // select current key |             // select current key | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								frontend/src/components/icons/Code.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								frontend/src/components/icons/Code.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | <script setup> | ||||||
|  | const props = defineProps({ | ||||||
|  |     strokeWidth: { | ||||||
|  |         type: [Number, String], | ||||||
|  |         default: 3, | ||||||
|  |     }, | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M40 23V14L31 4H10C8.89543 4 8 4.89543 8 6V42C8 43.1046 8.89543 44 10 44H22" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M37 31L42 36L37 41" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M31 31L26 36L31 41" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M30 4V14H40" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |     </svg> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped></style> | ||||||
							
								
								
									
										51
									
								
								frontend/src/components/icons/Conversion.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								frontend/src/components/icons/Conversion.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | <script setup> | ||||||
|  | const props = defineProps({ | ||||||
|  |     strokeWidth: { | ||||||
|  |         type: [Number, String], | ||||||
|  |         default: 3, | ||||||
|  |     }, | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M40 23V14L31 4H10C8.89543 4 8 4.89543 8 6V42C8 43.1046 8.89543 44 10 44H22" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M27 33H41" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M27 39H41" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M41 33L36 28" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M32 44L27 39" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M30 4V14H40" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |     </svg> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped></style> | ||||||
| @@ -12,7 +12,7 @@ const props = defineProps({ | |||||||
|         <path |         <path | ||||||
|             :stroke-width="props.strokeWidth" |             :stroke-width="props.strokeWidth" | ||||||
|             d="M7 42H43" |             d="M7 42H43" | ||||||
|             stroke="currentColorr" |             stroke="currentColor" | ||||||
|             stroke-linecap="round" |             stroke-linecap="round" | ||||||
|             stroke-linejoin="round" /> |             stroke-linejoin="round" /> | ||||||
|         <path |         <path | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								frontend/src/components/icons/EditFile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								frontend/src/components/icons/EditFile.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | <script setup> | ||||||
|  | const props = defineProps({ | ||||||
|  |     strokeWidth: { | ||||||
|  |         type: [Number, String], | ||||||
|  |         default: 3, | ||||||
|  |     }, | ||||||
|  | }) | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <template> | ||||||
|  |     <svg fill="none" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M40 23V14L31 4H10C8.89543 4 8 4.89543 8 6V42C8 43.1046 8.89543 44 10 44H22" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M32 44L42 34L38 30L28 40V44H32Z" | ||||||
|  |             fill="none" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |         <path | ||||||
|  |             :stroke-width="props.strokeWidth" | ||||||
|  |             d="M30 4V14H40" | ||||||
|  |             stroke="currentColor" | ||||||
|  |             stroke-linecap="round" | ||||||
|  |             stroke-linejoin="round" /> | ||||||
|  |     </svg> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped></style> | ||||||
| @@ -1,18 +1,24 @@ | |||||||
| /** | /** | ||||||
|  * string view mode |  * string format types | ||||||
|  * @enum {string} |  * @enum {string} | ||||||
|  */ |  */ | ||||||
| export const types = { | export const formatTypes = { | ||||||
|     PLAIN_TEXT: 'Plain Text', |     PLAIN_TEXT: 'Plain Text', | ||||||
|     JSON: 'JSON', |     JSON: 'JSON', | ||||||
|     BASE64_TEXT: 'Base64 Text', |  | ||||||
|     BASE64_JSON: 'Base64 JSON', |  | ||||||
|     HEX: 'Hex', |     HEX: 'Hex', | ||||||
|     BINARY: 'Binary', |     BINARY: 'Binary', | ||||||
|     GZIP: 'GZip', | } | ||||||
|     GZIP_JSON: 'GZip JSON', |  | ||||||
|     DEFLATE: 'Deflate', | /** | ||||||
|     DEFLATE_JSON: 'Deflate JSON', |  * string decode types | ||||||
|     BROTLI: 'Brotli', |  * @enum {string} | ||||||
|     BROTLI_JSON: 'Brotli JSON', |  */ | ||||||
|  | export const decodeTypes = { | ||||||
|  |     NONE: 'None', | ||||||
|  |     BASE64: 'Base64', | ||||||
|  |     GZIP: 'GZip', | ||||||
|  |     DEFLATE: 'Deflate', | ||||||
|  |     BROTLI: 'Brotli', | ||||||
|  |     // PHP: 'PHP', | ||||||
|  |     // Java: 'Java', | ||||||
| } | } | ||||||
|   | |||||||
| @@ -76,6 +76,7 @@ | |||||||
|     "filter_field": "Filter Field", |     "filter_field": "Filter Field", | ||||||
|     "filter_value": "Filter Value", |     "filter_value": "Filter Value", | ||||||
|     "view_as": "View As", |     "view_as": "View As", | ||||||
|  |     "decode_with": "Decode/Decompress Type", | ||||||
|     "reload": "Reload", |     "reload": "Reload", | ||||||
|     "open_connection": "Open Connection", |     "open_connection": "Open Connection", | ||||||
|     "batch_delete": "Batch Delete", |     "batch_delete": "Batch Delete", | ||||||
|   | |||||||
| @@ -76,6 +76,7 @@ | |||||||
|     "filter_field": "筛选字段", |     "filter_field": "筛选字段", | ||||||
|     "filter_value": "筛选值", |     "filter_value": "筛选值", | ||||||
|     "view_as": "查看方式", |     "view_as": "查看方式", | ||||||
|  |     "decode_with": "解码/解压方式", | ||||||
|     "reload": "重新载入", |     "reload": "重新载入", | ||||||
|     "open_connection": "打开连接", |     "open_connection": "打开连接", | ||||||
|     "batch_delete": "批量删除键", |     "batch_delete": "批量删除键", | ||||||
|   | |||||||
| @@ -657,14 +657,15 @@ const useConnectionStore = defineStore('connections', { | |||||||
|          * @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] when key is null or blank, update tab to display normal content (blank content or server status) | ||||||
|          * @param {string} [viewType] |          * @param {string} [viewType] | ||||||
|  |          * @param {string} [decodeType] | ||||||
|          */ |          */ | ||||||
|         async loadKeyValue(server, db, key, viewType) { |         async loadKeyValue(server, db, key, viewType, decodeType) { | ||||||
|             try { |             try { | ||||||
|                 const tab = useTabStore() |                 const tab = useTabStore() | ||||||
|                 if (!isEmpty(key)) { |                 if (!isEmpty(key)) { | ||||||
|                     const { data, success, msg } = await GetKeyValue(server, db, key, viewType) |                     const { data, success, msg } = await GetKeyValue(server, db, key, viewType, decodeType) | ||||||
|                     if (success) { |                     if (success) { | ||||||
|                         const { type, ttl, value, size, viewAs } = data |                         const { type, ttl, value, size, viewAs, decode } = data | ||||||
|                         const k = decodeRedisKey(key) |                         const k = decodeRedisKey(key) | ||||||
|                         const binaryKey = k !== key |                         const binaryKey = k !== key | ||||||
|                         tab.upsertTab({ |                         tab.upsertTab({ | ||||||
| @@ -678,6 +679,7 @@ const useConnectionStore = defineStore('connections', { | |||||||
|                             value, |                             value, | ||||||
|                             size, |                             size, | ||||||
|                             viewAs, |                             viewAs, | ||||||
|  |                             decode, | ||||||
|                         }) |                         }) | ||||||
|                         return |                         return | ||||||
|                     } else { |                     } else { | ||||||
| @@ -1092,11 +1094,12 @@ const useConnectionStore = defineStore('connections', { | |||||||
|          * @param {any} value |          * @param {any} value | ||||||
|          * @param {number} ttl |          * @param {number} ttl | ||||||
|          * @param {string} [viewAs] |          * @param {string} [viewAs] | ||||||
|  |          * @param {string} [decode] | ||||||
|          * @returns {Promise<{[msg]: string, success: boolean, [nodeKey]: {string}}>} |          * @returns {Promise<{[msg]: string, success: boolean, [nodeKey]: {string}}>} | ||||||
|          */ |          */ | ||||||
|         async setKey(connName, db, key, keyType, value, ttl, viewAs) { |         async setKey(connName, db, key, keyType, value, ttl, viewAs, decode) { | ||||||
|             try { |             try { | ||||||
|                 const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl, viewAs) |                 const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl, viewAs, decode) | ||||||
|                 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) | ||||||
|   | |||||||
| @@ -94,8 +94,9 @@ const useTabStore = defineStore('tab', { | |||||||
|          * @param {number} [size] |          * @param {number} [size] | ||||||
|          * @param {*} [value] |          * @param {*} [value] | ||||||
|          * @param {string} [viewAs] |          * @param {string} [viewAs] | ||||||
|  |          * @param {string} [decode] | ||||||
|          */ |          */ | ||||||
|         upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, value, viewAs }) { |         upsertTab({ subTab, server, db, type, ttl, key, keyCode, size, value, viewAs, decode }) { | ||||||
|             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({ | ||||||
| @@ -111,6 +112,7 @@ const useTabStore = defineStore('tab', { | |||||||
|                     size, |                     size, | ||||||
|                     value, |                     value, | ||||||
|                     viewAs, |                     viewAs, | ||||||
|  |                     decode, | ||||||
|                 }) |                 }) | ||||||
|                 tabIndex = this.tabList.length - 1 |                 tabIndex = this.tabList.length - 1 | ||||||
|             } else { |             } else { | ||||||
| @@ -128,6 +130,7 @@ const useTabStore = defineStore('tab', { | |||||||
|                 tab.size = size |                 tab.size = size | ||||||
|                 tab.value = value |                 tab.value = value | ||||||
|                 tab.viewAs = viewAs |                 tab.viewAs = viewAs | ||||||
|  |                 tab.decode = decode | ||||||
|             } |             } | ||||||
|             this._setActivatedIndex(tabIndex, true, subTab) |             this._setActivatedIndex(tabIndex, true, subTab) | ||||||
|             // this.activatedTab = tab.name |             // this.activatedTab = tab.name | ||||||
|   | |||||||
| @@ -79,7 +79,8 @@ body { | |||||||
|   flex-grow: 1; |   flex-grow: 1; | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|   gap: 5px; |   gap: 5px; | ||||||
|   padding: 5px; |   padding-top: 5px; | ||||||
|  |   //padding: 5px; | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
|  |  | ||||||
|   .tb2 { |   .tb2 { | ||||||
| @@ -97,6 +98,17 @@ body { | |||||||
|     user-select: text; |     user-select: text; | ||||||
|     height: 100%; |     height: 100%; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   .value-item-part { | ||||||
|  |     padding: 0 5px; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .value-footer { | ||||||
|  |     align-items: center; | ||||||
|  |     gap: 0; | ||||||
|  |     padding: 3px 10px 3px 10px; | ||||||
|  |     min-height: 30px; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| .n-dynamic-input-item { | .n-dynamic-input-item { | ||||||
| @@ -113,14 +125,27 @@ body { | |||||||
|   padding-right: 10px; |   padding-right: 10px; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .type-selector-header { | ||||||
|  |   height: 30px; | ||||||
|  |   line-height: 30px; | ||||||
|  |   font-size: 15px; | ||||||
|  |   font-weight: bold; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .type-selector-item { | ||||||
|  |   min-width: 100px; | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
| .nav-pane-container { | .nav-pane-container { | ||||||
|   overflow: hidden; |   overflow: hidden; | ||||||
|  |  | ||||||
|   .nav-pane-bottom { |   .nav-pane-bottom { | ||||||
|     align-items: center; |     align-items: center; | ||||||
|     gap: 8px; |     gap: 8px; | ||||||
|     padding: 3px 5px 5px 5px; |     padding: 3px 10px 3px 10px; | ||||||
|     min-height: 35px; |     min-height: 30px; | ||||||
|     //border-top: v-bind('themeVars.borderColor') 1px solid; |     //border-top: v-bind('themeVars.borderColor') 1px solid; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,8 +16,15 @@ export const themeOverrides = { | |||||||
|         scrollbarWidth: '8px', |         scrollbarWidth: '8px', | ||||||
|         tabColor: '#FFFFFF', |         tabColor: '#FFFFFF', | ||||||
|     }, |     }, | ||||||
|  |     Button: { | ||||||
|  |         heightMedium: '32px', | ||||||
|  |     }, | ||||||
|     Tag: { |     Tag: { | ||||||
|         // borderRadius: '3px' |         // borderRadius: '3px' | ||||||
|  |         heightLarge: '32px', | ||||||
|  |     }, | ||||||
|  |     Input: { | ||||||
|  |         heightMedium: '32px', | ||||||
|     }, |     }, | ||||||
|     Tabs: { |     Tabs: { | ||||||
|         tabGapSmallCard: '2px', |         tabGapSmallCard: '2px', | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 tiny-craft
					tiny-craft