Compare commits

...

10 Commits

Author SHA1 Message Date
Lykin bc66c63b3d chore: update dependencies 2024-01-08 00:26:57 +08:00
Lykin 72ef406cd8 fix: tooltips exceed the display area #112 2024-01-08 00:26:01 +08:00
Lykin 51a1b1b35f perf: disable tab switch after layer node selected 2024-01-08 00:01:52 +08:00
Lykin e87b02e14c perf: add an 'Import Keys' option to new key dialog. 2024-01-06 23:44:18 +08:00
Lykin 0d6765a757 perf: scroll to bottom when append item in new key dialog 2024-01-06 23:08:06 +08:00
Lykin ed1b9d9b54 fix: create key error 2024-01-06 22:13:26 +08:00
Lykin d66d7c9a49 feat: support key auto refresh
refactor: move 'ContentToolbar' to 'ContentValueWrapper'
2024-01-06 17:14:35 +08:00
Lykin c2bf4128f7 fix: minor bugs about keys view 2024-01-06 01:26:18 +08:00
Lykin 8d7c8cb3ed perf: support display layer with blank label 2024-01-05 22:02:23 +08:00
Lykin 9b3f2ba726 perf: move expanded keys to tab store for external modification 2024-01-05 20:48:57 +08:00
34 changed files with 632 additions and 412 deletions

View File

@ -685,6 +685,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
var data types.KeyDetail var data types.KeyDetail
data.KeyType = strings.ToLower(keyType)
//var cursor uint64 //var cursor uint64
matchPattern := param.MatchPattern matchPattern := param.MatchPattern
if len(matchPattern) <= 0 { if len(matchPattern) <= 0 {
@ -728,7 +729,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
} }
} }
switch strings.ToLower(keyType) { switch data.KeyType {
case "string": case "string":
var str string var str string
str, err = client.Get(ctx, key).Result() str, err = client.Get(ctx, key).Result()
@ -2293,7 +2294,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
continue continue
} }
// get ttl // get ttl
if ttl < 0 { if ttl < 0 && len(line) > 2 {
// use previous // use previous
if expire, ttlErr := strconv.ParseInt(line[2], 10, 64); ttlErr == nil && expire > 0 { if expire, ttlErr := strconv.ParseInt(line[2], 10, 64); ttlErr == nil && expire > 0 {
ttlValue = time.UnixMilli(expire).Sub(time.Now()) ttlValue = time.UnixMilli(expire).Sub(time.Now())

View File

@ -31,13 +31,14 @@ type KeyDetailParam struct {
} }
type KeyDetail struct { type KeyDetail struct {
Value any `json:"value"` Value any `json:"value"`
Length int64 `json:"length,omitempty"` KeyType string `json:"key_type"`
Format string `json:"format,omitempty"` Length int64 `json:"length,omitempty"`
Decode string `json:"decode,omitempty"` Format string `json:"format,omitempty"`
Match string `json:"match,omitempty"` Decode string `json:"decode,omitempty"`
Reset bool `json:"reset"` Match string `json:"match,omitempty"`
End bool `json:"end"` Reset bool `json:"reset"`
End bool `json:"end"`
} }
type SetKeyParam struct { type SetKeyParam struct {

View File

@ -13,20 +13,20 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.45.0", "monaco-editor": "^0.45.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"sass": "^1.69.6", "sass": "^1.69.7",
"vue": "^3.4.3", "vue": "^3.4.5",
"vue-i18n": "^9.8.0", "vue-i18n": "^9.9.0",
"xterm": "^5.3.0", "xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0" "xterm-addon-fit": "^0.8.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.2", "@vitejs/plugin-vue": "^5.0.2",
"naive-ui": "^2.36.0", "naive-ui": "^2.37.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"unplugin-auto-import": "^0.17.3", "unplugin-auto-import": "^0.17.3",
"unplugin-icons": "^0.18.1", "unplugin-icons": "^0.18.1",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"vite": "^5.0.10" "vite": "^5.0.11"
} }
}, },
"node_modules/@antfu/install-pkg": { "node_modules/@antfu/install-pkg": {
@ -572,35 +572,44 @@
} }
}, },
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.8.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.9.0.tgz",
"integrity": "sha512-UxaSZVZ1DwqC/CltUZrWZNaWNhfmKtfyV4BJSt/Zt4Or/fZs1iFj0B+OekYk1+MRHfIOe3+x00uXGQI4PbO/9g==", "integrity": "sha512-C7UXPymDIOlMGSNjAhNLtKgzITc/8BjINK5gNKXg8GiWCTwL6n3MWr55czksxn8RM5wTMz0qcLOFT+adtaVQaA==",
"dependencies": { "dependencies": {
"@intlify/message-compiler": "9.8.0", "@intlify/message-compiler": "9.9.0",
"@intlify/shared": "9.8.0" "@intlify/shared": "9.9.0"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
} }
}, },
"node_modules/@intlify/message-compiler": { "node_modules/@intlify/message-compiler": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.8.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.9.0.tgz",
"integrity": "sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ==", "integrity": "sha512-yDU/jdUm9KuhEzYfS+wuyja209yXgdl1XFhMlKtXEgSFTxz4COZQCRXXbbH8JrAjMsaJ7bdoPSLsKlY6mXG2iA==",
"dependencies": { "dependencies": {
"@intlify/shared": "9.8.0", "@intlify/shared": "9.9.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
} }
}, },
"node_modules/@intlify/shared": { "node_modules/@intlify/shared": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.8.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.9.0.tgz",
"integrity": "sha512-TmgR0RCLjzrSo+W3wT0ALf9851iFMlVI9EYNGeWvZFUQTAJx0bvfsMlPdgVtV1tDNRiAfhkFsMKu6jtUY1ZLKQ==", "integrity": "sha512-1ECUyAHRrzOJbOizyGufYP2yukqGrWXtkmTu4PcswVnWbkcjzk3YQGmJ0bLkM7JZ0ZYAaohLGdYvBYnTOGYJ9g==",
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
} }
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
@ -868,36 +877,36 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.5.tgz",
"integrity": "sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==", "integrity": "sha512-Daka7P1z2AgKjzuueWXhwzIsKu0NkLB6vGbNVEV2iJ8GJTrzraZo/Sk4GWCMRtd/qVi3zwnk+Owbd/xSZbwHtQ==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.6",
"@vue/shared": "3.4.3", "@vue/shared": "3.4.5",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.5.tgz",
"integrity": "sha512-oGF1E9/htI6JWj/lTJgr6UgxNCtNHbM6xKVreBWeZL9QhRGABRVoWGAzxmtBfSOd+w0Zi5BY0Es/tlJrN6WgEg==", "integrity": "sha512-J8YlxknJVd90SXFJ4HwGANSAXsx5I0lK30sO/zvYV7s5gXf7gZR7r/1BmZ2ju7RGH1lnc6bpBc6nL61yW+PsAQ==",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.4.3", "@vue/compiler-core": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.5.tgz",
"integrity": "sha512-NuJqb5is9I4uzv316VRUDYgIlPZCG8D+ARt5P4t5UDShIHKL25J3TGZAUryY/Aiy0DsY7srJnZL5ryB6DD63Zw==", "integrity": "sha512-jauvkDuSSUbP0ebhfNqljhShA90YEfX/0wZ+w40oZF43IjGyWYjqYaJbvMJwGOd+9+vODW6eSvnk28f0SGV7OQ==",
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.6",
"@vue/compiler-core": "3.4.3", "@vue/compiler-core": "3.4.5",
"@vue/compiler-dom": "3.4.3", "@vue/compiler-dom": "3.4.5",
"@vue/compiler-ssr": "3.4.3", "@vue/compiler-ssr": "3.4.5",
"@vue/shared": "3.4.3", "@vue/shared": "3.4.5",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.5", "magic-string": "^0.30.5",
"postcss": "^8.4.32", "postcss": "^8.4.32",
@ -905,12 +914,12 @@
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.5.tgz",
"integrity": "sha512-wnYQtMBkeFSxgSSQbYGQeXPhQacQiog2c6AlvMldQH6DB+gSXK/0F6DVXAJfEiuBSgBhUc8dwrrG5JQcqwalsA==", "integrity": "sha512-DDdEcDzj2lWTMfUMMtEpLDhURai9LhM0zSZ219jCt7b2Vyl0/jy3keFgCPMitG0V1S1YG4Cmws3lWHWdxHQOpg==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.3", "@vue/compiler-dom": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@ -919,29 +928,29 @@
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz",
"integrity": "sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg==", "integrity": "sha512-BcWkKvjdvqJwb7BhhFkXPLDCecX4d4a6GATvCduJQDLv21PkPowAE5GKuIE5p6RC07/Lp9FMkkq4AYCTVF5KlQ==",
"dependencies": { "dependencies": {
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.5.tgz",
"integrity": "sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ==", "integrity": "sha512-wh9ELIOQKeWT9SaUPdLrsxRkZv14jp+SJm9aiQGWio+/MWNM3Lib0wE6CoKEqQ9+SCYyGjDBhTOTtO47kCgbkg==",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.4.3", "@vue/reactivity": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.5.tgz",
"integrity": "sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A==", "integrity": "sha512-n5ewvOjyG3IEpqGBahdPXODFSpVlSz3H4LF76Sx0XAqpIOqyJ5bIb2PrdYuH2ogBMAQPh+o5tnoH4nJpBr8U0Q==",
"dependencies": { "dependencies": {
"@vue/runtime-core": "3.4.3", "@vue/runtime-core": "3.4.5",
"@vue/shared": "3.4.3", "@vue/shared": "3.4.5",
"csstype": "^3.1.3" "csstype": "^3.1.3"
} }
}, },
@ -951,21 +960,21 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.5.tgz",
"integrity": "sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw==", "integrity": "sha512-jOFc/VE87yvifQpNju12VcqimH8pBLxdcT+t3xMeiED1K6DfH9SORyhFEoZlW5TG2Vwfn3Ul5KE+1aC99xnSBg==",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.4.3", "@vue/compiler-ssr": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.4.3" "vue": "3.4.5"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.5.tgz",
"integrity": "sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==" "integrity": "sha512-6XptuzlMvN4l4cDnDw36pdGEV+9njYkQ1ZE0Q6iZLwrKefKaOJyiFmcP3/KBDHbt72cJZGtllAc1GaHe6XGAyg=="
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.11.2", "version": "8.11.2",
@ -1536,9 +1545,9 @@
"dev": true "dev": true
}, },
"node_modules/naive-ui": { "node_modules/naive-ui": {
"version": "2.36.0", "version": "2.37.0",
"resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.36.0.tgz", "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.37.0.tgz",
"integrity": "sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==", "integrity": "sha512-TcuXM1zysnK6i/7o2ZqNjcLp3QMmcdSLWWiXcpEk+xdGpkJzs53/OXNpF4CoDM/npjha7qqtB8Pl17YPN5egFw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@css-render/plugin-bem": "^0.15.12", "@css-render/plugin-bem": "^0.15.12",
@ -1548,6 +1557,7 @@
"@types/lodash-es": "^4.17.9", "@types/lodash-es": "^4.17.9",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"css-render": "^0.15.12", "css-render": "^0.15.12",
"csstype": "^3.1.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0", "date-fns-tz": "^2.0.0",
"evtd": "^0.2.4", "evtd": "^0.2.4",
@ -1558,12 +1568,18 @@
"treemate": "^0.3.11", "treemate": "^0.3.11",
"vdirs": "^0.1.8", "vdirs": "^0.1.8",
"vooks": "^0.2.12", "vooks": "^0.2.12",
"vueuc": "^0.4.54" "vueuc": "^0.4.58"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "^3.0.0" "vue": "^3.0.0"
} }
}, },
"node_modules/naive-ui/node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.7",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
@ -1851,9 +1867,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.69.6", "version": "1.69.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.6.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
"integrity": "sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==", "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0", "immutable": "^4.0.0",
@ -2173,9 +2189,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.0.10", "version": "5.0.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz",
"integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.19.3", "esbuild": "^0.19.3",
@ -2240,15 +2256,15 @@
} }
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.3.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.5.tgz",
"integrity": "sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==", "integrity": "sha512-VH6nHFhLPjgu2oh5vEBXoNZxsGHuZNr3qf4PHClwJWw6IDqw6B3x+4J+ABdoZ0aJuT8Zi0zf3GpGlLQCrGWHrw==",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.4.3", "@vue/compiler-dom": "3.4.5",
"@vue/compiler-sfc": "3.4.3", "@vue/compiler-sfc": "3.4.5",
"@vue/runtime-dom": "3.4.3", "@vue/runtime-dom": "3.4.5",
"@vue/server-renderer": "3.4.3", "@vue/server-renderer": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@ -2260,25 +2276,28 @@
} }
}, },
"node_modules/vue-i18n": { "node_modules/vue-i18n": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.8.0.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.0.tgz",
"integrity": "sha512-Izho+6PYjejsTq2mzjcRdBZ5VLRQoSuuexvR8029h5CpN03FYqiqBrShMyf2I1DKkN6kw/xmujcbvC+4QybpsQ==", "integrity": "sha512-xQ5SxszUAqK5n84N+uUyHH/PiQl9xZ24FOxyAaNonmOQgXeN+rD9z/6DStOpOxNFQn4Cgcquot05gZc+CdOujA==",
"dependencies": { "dependencies": {
"@intlify/core-base": "9.8.0", "@intlify/core-base": "9.9.0",
"@intlify/shared": "9.8.0", "@intlify/shared": "9.9.0",
"@vue/devtools-api": "^6.5.0" "@vue/devtools-api": "^6.5.0"
}, },
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
}, },
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": { "peerDependencies": {
"vue": "^3.0.0" "vue": "^3.0.0"
} }
}, },
"node_modules/vueuc": { "node_modules/vueuc": {
"version": "0.4.54", "version": "0.4.58",
"resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.54.tgz", "resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.58.tgz",
"integrity": "sha512-2LED7h1BSnCRPBI6AlSIf+1Yte1shN+Vb2gpspO5wHI7zWzbcq4bAu2f9nFh5yXIUKdzqmLvzRsOXDl4TrDyCw==", "integrity": "sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@css-render/vue3-ssr": "^0.15.10", "@css-render/vue3-ssr": "^0.15.10",
@ -2654,27 +2673,27 @@
} }
}, },
"@intlify/core-base": { "@intlify/core-base": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.8.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.9.0.tgz",
"integrity": "sha512-UxaSZVZ1DwqC/CltUZrWZNaWNhfmKtfyV4BJSt/Zt4Or/fZs1iFj0B+OekYk1+MRHfIOe3+x00uXGQI4PbO/9g==", "integrity": "sha512-C7UXPymDIOlMGSNjAhNLtKgzITc/8BjINK5gNKXg8GiWCTwL6n3MWr55czksxn8RM5wTMz0qcLOFT+adtaVQaA==",
"requires": { "requires": {
"@intlify/message-compiler": "9.8.0", "@intlify/message-compiler": "9.9.0",
"@intlify/shared": "9.8.0" "@intlify/shared": "9.9.0"
} }
}, },
"@intlify/message-compiler": { "@intlify/message-compiler": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.8.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.9.0.tgz",
"integrity": "sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ==", "integrity": "sha512-yDU/jdUm9KuhEzYfS+wuyja209yXgdl1XFhMlKtXEgSFTxz4COZQCRXXbbH8JrAjMsaJ7bdoPSLsKlY6mXG2iA==",
"requires": { "requires": {
"@intlify/shared": "9.8.0", "@intlify/shared": "9.9.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
}, },
"@intlify/shared": { "@intlify/shared": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.8.0.tgz", "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.9.0.tgz",
"integrity": "sha512-TmgR0RCLjzrSo+W3wT0ALf9851iFMlVI9EYNGeWvZFUQTAJx0bvfsMlPdgVtV1tDNRiAfhkFsMKu6jtUY1ZLKQ==" "integrity": "sha512-1ECUyAHRrzOJbOizyGufYP2yukqGrWXtkmTu4PcswVnWbkcjzk3YQGmJ0bLkM7JZ0ZYAaohLGdYvBYnTOGYJ9g=="
}, },
"@jridgewell/sourcemap-codec": { "@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",
@ -2843,36 +2862,36 @@
"requires": {} "requires": {}
}, },
"@vue/compiler-core": { "@vue/compiler-core": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.5.tgz",
"integrity": "sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==", "integrity": "sha512-Daka7P1z2AgKjzuueWXhwzIsKu0NkLB6vGbNVEV2iJ8GJTrzraZo/Sk4GWCMRtd/qVi3zwnk+Owbd/xSZbwHtQ==",
"requires": { "requires": {
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.6",
"@vue/shared": "3.4.3", "@vue/shared": "3.4.5",
"entities": "^4.5.0", "entities": "^4.5.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
} }
}, },
"@vue/compiler-dom": { "@vue/compiler-dom": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.5.tgz",
"integrity": "sha512-oGF1E9/htI6JWj/lTJgr6UgxNCtNHbM6xKVreBWeZL9QhRGABRVoWGAzxmtBfSOd+w0Zi5BY0Es/tlJrN6WgEg==", "integrity": "sha512-J8YlxknJVd90SXFJ4HwGANSAXsx5I0lK30sO/zvYV7s5gXf7gZR7r/1BmZ2ju7RGH1lnc6bpBc6nL61yW+PsAQ==",
"requires": { "requires": {
"@vue/compiler-core": "3.4.3", "@vue/compiler-core": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"@vue/compiler-sfc": { "@vue/compiler-sfc": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.5.tgz",
"integrity": "sha512-NuJqb5is9I4uzv316VRUDYgIlPZCG8D+ARt5P4t5UDShIHKL25J3TGZAUryY/Aiy0DsY7srJnZL5ryB6DD63Zw==", "integrity": "sha512-jauvkDuSSUbP0ebhfNqljhShA90YEfX/0wZ+w40oZF43IjGyWYjqYaJbvMJwGOd+9+vODW6eSvnk28f0SGV7OQ==",
"requires": { "requires": {
"@babel/parser": "^7.23.6", "@babel/parser": "^7.23.6",
"@vue/compiler-core": "3.4.3", "@vue/compiler-core": "3.4.5",
"@vue/compiler-dom": "3.4.3", "@vue/compiler-dom": "3.4.5",
"@vue/compiler-ssr": "3.4.3", "@vue/compiler-ssr": "3.4.5",
"@vue/shared": "3.4.3", "@vue/shared": "3.4.5",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.5", "magic-string": "^0.30.5",
"postcss": "^8.4.32", "postcss": "^8.4.32",
@ -2880,12 +2899,12 @@
} }
}, },
"@vue/compiler-ssr": { "@vue/compiler-ssr": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.5.tgz",
"integrity": "sha512-wnYQtMBkeFSxgSSQbYGQeXPhQacQiog2c6AlvMldQH6DB+gSXK/0F6DVXAJfEiuBSgBhUc8dwrrG5JQcqwalsA==", "integrity": "sha512-DDdEcDzj2lWTMfUMMtEpLDhURai9LhM0zSZ219jCt7b2Vyl0/jy3keFgCPMitG0V1S1YG4Cmws3lWHWdxHQOpg==",
"requires": { "requires": {
"@vue/compiler-dom": "3.4.3", "@vue/compiler-dom": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"@vue/devtools-api": { "@vue/devtools-api": {
@ -2894,29 +2913,29 @@
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
}, },
"@vue/reactivity": { "@vue/reactivity": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.5.tgz",
"integrity": "sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg==", "integrity": "sha512-BcWkKvjdvqJwb7BhhFkXPLDCecX4d4a6GATvCduJQDLv21PkPowAE5GKuIE5p6RC07/Lp9FMkkq4AYCTVF5KlQ==",
"requires": { "requires": {
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"@vue/runtime-core": { "@vue/runtime-core": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.5.tgz",
"integrity": "sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ==", "integrity": "sha512-wh9ELIOQKeWT9SaUPdLrsxRkZv14jp+SJm9aiQGWio+/MWNM3Lib0wE6CoKEqQ9+SCYyGjDBhTOTtO47kCgbkg==",
"requires": { "requires": {
"@vue/reactivity": "3.4.3", "@vue/reactivity": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"@vue/runtime-dom": { "@vue/runtime-dom": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.5.tgz",
"integrity": "sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A==", "integrity": "sha512-n5ewvOjyG3IEpqGBahdPXODFSpVlSz3H4LF76Sx0XAqpIOqyJ5bIb2PrdYuH2ogBMAQPh+o5tnoH4nJpBr8U0Q==",
"requires": { "requires": {
"@vue/runtime-core": "3.4.3", "@vue/runtime-core": "3.4.5",
"@vue/shared": "3.4.3", "@vue/shared": "3.4.5",
"csstype": "^3.1.3" "csstype": "^3.1.3"
}, },
"dependencies": { "dependencies": {
@ -2928,18 +2947,18 @@
} }
}, },
"@vue/server-renderer": { "@vue/server-renderer": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.5.tgz",
"integrity": "sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw==", "integrity": "sha512-jOFc/VE87yvifQpNju12VcqimH8pBLxdcT+t3xMeiED1K6DfH9SORyhFEoZlW5TG2Vwfn3Ul5KE+1aC99xnSBg==",
"requires": { "requires": {
"@vue/compiler-ssr": "3.4.3", "@vue/compiler-ssr": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"@vue/shared": { "@vue/shared": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.5.tgz",
"integrity": "sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==" "integrity": "sha512-6XptuzlMvN4l4cDnDw36pdGEV+9njYkQ1ZE0Q6iZLwrKefKaOJyiFmcP3/KBDHbt72cJZGtllAc1GaHe6XGAyg=="
}, },
"acorn": { "acorn": {
"version": "8.11.2", "version": "8.11.2",
@ -3382,9 +3401,9 @@
"dev": true "dev": true
}, },
"naive-ui": { "naive-ui": {
"version": "2.36.0", "version": "2.37.0",
"resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.36.0.tgz", "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.37.0.tgz",
"integrity": "sha512-r1ydtEm1Ryf/aWpbLCf32mQAGK99jd1eXgpkCtIomcBRZeAtusfy6zCtIpCppoCuIKM3BW5DMafhVxilubk/lQ==", "integrity": "sha512-TcuXM1zysnK6i/7o2ZqNjcLp3QMmcdSLWWiXcpEk+xdGpkJzs53/OXNpF4CoDM/npjha7qqtB8Pl17YPN5egFw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@css-render/plugin-bem": "^0.15.12", "@css-render/plugin-bem": "^0.15.12",
@ -3394,6 +3413,7 @@
"@types/lodash-es": "^4.17.9", "@types/lodash-es": "^4.17.9",
"async-validator": "^4.2.5", "async-validator": "^4.2.5",
"css-render": "^0.15.12", "css-render": "^0.15.12",
"csstype": "^3.1.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0", "date-fns-tz": "^2.0.0",
"evtd": "^0.2.4", "evtd": "^0.2.4",
@ -3404,7 +3424,15 @@
"treemate": "^0.3.11", "treemate": "^0.3.11",
"vdirs": "^0.1.8", "vdirs": "^0.1.8",
"vooks": "^0.2.12", "vooks": "^0.2.12",
"vueuc": "^0.4.54" "vueuc": "^0.4.58"
},
"dependencies": {
"csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
}
} }
}, },
"nanoid": { "nanoid": {
@ -3607,9 +3635,9 @@
} }
}, },
"sass": { "sass": {
"version": "1.69.6", "version": "1.69.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.6.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
"integrity": "sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==", "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
"requires": { "requires": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0", "immutable": "^4.0.0",
@ -3832,9 +3860,9 @@
} }
}, },
"vite": { "vite": {
"version": "5.0.10", "version": "5.0.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz",
"integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", "integrity": "sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==",
"dev": true, "dev": true,
"requires": { "requires": {
"esbuild": "^0.19.3", "esbuild": "^0.19.3",
@ -3853,31 +3881,31 @@
} }
}, },
"vue": { "vue": {
"version": "3.4.3", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.3.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.5.tgz",
"integrity": "sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==", "integrity": "sha512-VH6nHFhLPjgu2oh5vEBXoNZxsGHuZNr3qf4PHClwJWw6IDqw6B3x+4J+ABdoZ0aJuT8Zi0zf3GpGlLQCrGWHrw==",
"requires": { "requires": {
"@vue/compiler-dom": "3.4.3", "@vue/compiler-dom": "3.4.5",
"@vue/compiler-sfc": "3.4.3", "@vue/compiler-sfc": "3.4.5",
"@vue/runtime-dom": "3.4.3", "@vue/runtime-dom": "3.4.5",
"@vue/server-renderer": "3.4.3", "@vue/server-renderer": "3.4.5",
"@vue/shared": "3.4.3" "@vue/shared": "3.4.5"
} }
}, },
"vue-i18n": { "vue-i18n": {
"version": "9.8.0", "version": "9.9.0",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.8.0.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.0.tgz",
"integrity": "sha512-Izho+6PYjejsTq2mzjcRdBZ5VLRQoSuuexvR8029h5CpN03FYqiqBrShMyf2I1DKkN6kw/xmujcbvC+4QybpsQ==", "integrity": "sha512-xQ5SxszUAqK5n84N+uUyHH/PiQl9xZ24FOxyAaNonmOQgXeN+rD9z/6DStOpOxNFQn4Cgcquot05gZc+CdOujA==",
"requires": { "requires": {
"@intlify/core-base": "9.8.0", "@intlify/core-base": "9.9.0",
"@intlify/shared": "9.8.0", "@intlify/shared": "9.9.0",
"@vue/devtools-api": "^6.5.0" "@vue/devtools-api": "^6.5.0"
} }
}, },
"vueuc": { "vueuc": {
"version": "0.4.54", "version": "0.4.58",
"resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.54.tgz", "resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.58.tgz",
"integrity": "sha512-2LED7h1BSnCRPBI6AlSIf+1Yte1shN+Vb2gpspO5wHI7zWzbcq4bAu2f9nFh5yXIUKdzqmLvzRsOXDl4TrDyCw==", "integrity": "sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@css-render/vue3-ssr": "^0.15.10", "@css-render/vue3-ssr": "^0.15.10",

View File

@ -14,19 +14,19 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.45.0", "monaco-editor": "^0.45.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"sass": "^1.69.6", "sass": "^1.69.7",
"vue": "^3.4.3", "vue": "^3.4.5",
"vue-i18n": "^9.8.0", "vue-i18n": "^9.9.0",
"xterm": "^5.3.0", "xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0" "xterm-addon-fit": "^0.8.0"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.2", "@vitejs/plugin-vue": "^5.0.2",
"naive-ui": "^2.36.0", "naive-ui": "^2.37.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"unplugin-auto-import": "^0.17.3", "unplugin-auto-import": "^0.17.3",
"unplugin-icons": "^0.18.1", "unplugin-icons": "^0.18.1",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"vite": "^5.0.10" "vite": "^5.0.11"
} }
} }

View File

@ -1 +1 @@
bae1706c9c117d6fcce2d7bd649e127f 6dd5fc2cecb3eb5a0c7297245ac11808

View File

@ -16,7 +16,6 @@ const emit = defineEmits(['edit', 'delete', 'copy', 'save', 'cancel'])
</script> </script>
<template> <template>
<!-- TODO: support multiple save -->
<div v-if="props.editing" class="flex-box-h edit-column-func"> <div v-if="props.editing" class="flex-box-h edit-column-func">
<icon-button :icon="Save" @click="emit('save')" /> <icon-button :icon="Save" @click="emit('save')" />
<icon-button :icon="Close" @click="emit('cancel')" /> <icon-button :icon="Close" @click="emit('cancel')" />

View File

@ -16,6 +16,7 @@ import Pub from '@/components/icons/Pub.vue'
import ContentSlog from '@/components/content_value/ContentSlog.vue' import ContentSlog from '@/components/content_value/ContentSlog.vue'
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js' import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
import ContentMonitor from '@/components/content_value/ContentMonitor.vue' import ContentMonitor from '@/components/content_value/ContentMonitor.vue'
import { decodeRedisKey } from '@/utils/key_convert.js'
const themeVars = useThemeVars() const themeVars = useThemeVars()
@ -50,7 +51,7 @@ const tabContent = computed(() => {
subTab: tab.subTab, subTab: tab.subTab,
type: toUpper(tab.type), type: toUpper(tab.type),
db: tab.db, db: tab.db,
keyPath: tab.key, keyPath: tab.keyCode != null ? decodeRedisKey(tab.keyCode) : tab.key,
keyCode: tab.keyCode, keyCode: tab.keyCode,
ttl: tab.ttl, ttl: tab.ttl,
value: tab.value, value: tab.value,

View File

@ -61,6 +61,8 @@ const columns = computed(() => [
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '80vw',
maxHeight: '60vh',
overflowY: 'scroll',
}, },
}, },
}, },

View File

@ -10,8 +10,10 @@ import { useI18n } from 'vue-i18n'
import IconButton from '@/components/common/IconButton.vue' import IconButton from '@/components/common/IconButton.vue'
import Copy from '@/components/icons/Copy.vue' import Copy from '@/components/icons/Copy.vue'
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js' import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
import { computed } from 'vue' import { computed, onUnmounted, reactive, watch } from 'vue'
import { padStart } from 'lodash' import { padStart } from 'lodash'
import { NIcon, useThemeVars } from 'naive-ui'
import { timeout } from '@/utils/promise.js'
const props = defineProps({ const props = defineProps({
server: String, server: String,
@ -37,6 +39,12 @@ const props = defineProps({
const emit = defineEmits(['reload', 'rename', 'delete']) const emit = defineEmits(['reload', 'rename', 'delete'])
const autoRefresh = reactive({
on: false,
interval: 2,
})
const themeVars = useThemeVars()
const dialogStore = useDialog() const dialogStore = useDialog()
const i18n = useI18n() const i18n = useI18n()
@ -61,6 +69,48 @@ const ttlString = computed(() => {
return s return s
}) })
const startAutoRefresh = async () => {
if (autoRefresh.on) {
return
}
autoRefresh.on = true
let lastExec = Date.now()
do {
if (!autoRefresh.on) {
break
}
await timeout(100)
if (props.loading || Date.now() - lastExec < autoRefresh.interval * 1000) {
continue
}
lastExec = Date.now()
emit('reload')
} while (true)
stopAutoRefresh()
}
const stopAutoRefresh = () => {
autoRefresh.on = false
}
watch(
() => props.keyPath,
() => {
stopAutoRefresh()
autoRefresh.interval = props.interval
},
)
onUnmounted(() => stopAutoRefresh())
const onToggleRefresh = (on) => {
if (on) {
startAutoRefresh()
} else {
stopAutoRefresh()
}
}
const onCopyKey = () => { const onCopyKey = () => {
ClipboardSetText(props.keyPath) ClipboardSetText(props.keyPath)
.then((succ) => { .then((succ) => {
@ -87,14 +137,48 @@ const onTTL = () => {
<div class="content-toolbar flex-box-h"> <div class="content-toolbar flex-box-h">
<n-input-group> <n-input-group>
<redis-type-tag :binary-key="binaryKey" :type="props.keyType" size="large" /> <redis-type-tag :binary-key="binaryKey" :type="props.keyType" size="large" />
<n-input v-model:value="props.keyPath" :title="props.keyPath" readonly> <n-input v-model:value="props.keyPath" :title="props.keyPath" readonly @dblclick="onCopyKey">
<template #suffix> <template #suffix>
<icon-button <n-popover :delay="500" keep-alive-on-hover placement="bottom" trigger="hover">
:icon="Refresh" <template #trigger>
:loading="props.loading" <icon-button :loading="props.loading" size="18" @click="emit('reload')">
size="18" <n-icon :size="props.size">
t-tooltip="interface.reload" <component
@click="emit('reload')" /> :is="Refresh"
:class="{ 'auto-refreshing': autoRefresh.on }"
:color="autoRefresh.on ? themeVars.primaryColor : undefined"
:stroke-width="autoRefresh.on ? 5 : 3" />
</n-icon>
</icon-button>
</template>
<n-form
:show-feedback="false"
label-align="right"
label-placement="left"
label-width="auto"
size="small">
<n-form-item :label="$t('interface.auto_refresh')">
<n-switch
:loading="props.loading"
:value="autoRefresh.on"
@update:value="onToggleRefresh" />
</n-form-item>
<n-form-item :label="$t('interface.refresh_interval')">
<n-input-number
v-model:value="autoRefresh.interval"
:autofocus="false"
:disabled="autoRefresh.on"
:max="9999"
:min="1"
:show-button="false"
style="max-width: 100px">
<template #suffix>
{{ $t('common.unit_second') }}
</template>
</n-input-number>
</n-form-item>
</n-form>
</n-popover>
</template> </template>
</n-input> </n-input>
<icon-button :icon="Copy" border size="18" t-tooltip="interface.copy_key" @click="onCopyKey" /> <icon-button :icon="Copy" border size="18" t-tooltip="interface.copy_key" @click="onCopyKey" />
@ -111,7 +195,13 @@ const onTTL = () => {
</template> </template>
TTL{{ `${ttl > 0 ? ': ' + ttl + $t('common.second') : ''}` }} TTL{{ `${ttl > 0 ? ': ' + ttl + $t('common.second') : ''}` }}
</n-tooltip> </n-tooltip>
<icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="emit('rename')" /> <icon-button
:disabled="binaryKey"
:icon="Edit"
:t-tooltip="binaryKey ? 'dialogue.rename_binary_key_fail' : 'interface.rename_key'"
border
size="18"
@click="emit('rename')" />
</n-button-group> </n-button-group>
<n-tooltip :show-arrow="false"> <n-tooltip :show-arrow="false">
<template #trigger> <template #trigger>
@ -131,4 +221,14 @@ const onTTL = () => {
align-items: center; align-items: center;
gap: 5px; gap: 5px;
} }
.auto-refreshing {
animation: rotate 2s linear infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
</style> </style>

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { computed, h, reactive, ref } from 'vue' import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NIcon, useThemeVars } from 'naive-ui' import { NButton, NIcon, 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'
@ -54,7 +53,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'reload', 'match'])
/** /**
* *
@ -92,8 +91,11 @@ const fieldColumn = computed(() => ({
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '80vw',
maxHeight: '60vh',
overflowY: 'scroll',
}, },
}, },
lineClamp: 10,
}, },
filterOptionValue: fieldFilterOption.value, filterOptionValue: fieldFilterOption.value,
className: inEdit.value ? 'clickable' : '', className: inEdit.value ? 'clickable' : '',
@ -121,6 +123,8 @@ const valueColumn = computed(() => ({
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '80vw',
maxHeight: '60vh',
overflowY: 'scroll',
}, },
}, },
}, },
@ -326,18 +330,7 @@ defineExpose({
<template> <template>
<div class="content-wrapper flex-box-v"> <div class="content-wrapper flex-box-v">
<content-toolbar <slot name="toolbar" />
:db="props.db"
:key-code="props.keyCode"
:key-path="props.keyPath"
:key-type="keyType"
:loading="props.loading"
:server="props.name"
:ttl="ttl"
class="value-item-part"
@delete="emit('delete')"
@reload="emit('reload')"
@rename="emit('rename')" />
<div class="tb2 value-item-part flex-box-h"> <div class="tb2 value-item-part flex-box-h">
<div class="flex-box-h"> <div class="flex-box-h">
<content-search-input <content-search-input

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { computed, h, reactive, ref } from 'vue' import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NIcon, useThemeVars } from 'naive-ui' import { NButton, NIcon, useThemeVars } from 'naive-ui'
import { isEmpty, size } from 'lodash' import { isEmpty, size } from 'lodash'
@ -54,7 +53,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'reload', 'match'])
/** /**
* *
@ -93,6 +92,8 @@ const valueColumn = computed(() => ({
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '80vw',
maxHeight: '60vh',
overflowY: 'scroll',
}, },
}, },
}, },
@ -297,18 +298,7 @@ defineExpose({
<template> <template>
<div class="content-wrapper flex-box-v"> <div class="content-wrapper flex-box-v">
<content-toolbar <slot name="toolbar" />
:db="props.db"
:key-code="props.keyCode"
:key-path="props.keyPath"
:key-type="keyType"
:loading="props.loading"
:server="props.name"
:ttl="ttl"
class="value-item-part"
@delete="emit('delete')"
@reload="emit('reload')"
@rename="emit('rename')" />
<div class="tb2 value-item-part flex-box-h"> <div class="tb2 value-item-part flex-box-h">
<div class="flex-box-h"> <div class="flex-box-h">
<content-search-input <content-search-input

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { computed, h, reactive, ref } from 'vue' import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NIcon, useThemeVars } from 'naive-ui' import { NButton, NIcon, useThemeVars } from 'naive-ui'
import { isEmpty, size } from 'lodash' import { isEmpty, size } from 'lodash'
@ -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', 'match'])
/** /**
* *
@ -92,6 +91,8 @@ const valueColumn = computed(() => ({
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '80vw',
maxHeight: '60vh',
overflowY: 'scroll',
}, },
}, },
}, },
@ -294,18 +295,7 @@ defineExpose({
<template> <template>
<div class="content-wrapper flex-box-v"> <div class="content-wrapper flex-box-v">
<content-toolbar <slot name="toolbar" />
:db="props.db"
:key-code="props.keyCode"
:key-path="props.keyPath"
:key-type="keyType"
:loading="props.loading"
:server="props.name"
:ttl="ttl"
class="value-item-part"
@delete="emit('delete')"
@reload="emit('reload')"
@rename="emit('rename')" />
<div class="tb2 value-item-part flex-box-h"> <div class="tb2 value-item-part flex-box-h">
<div class="flex-box-h"> <div class="flex-box-h">
<content-search-input <content-search-input

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { computed, h, ref } from 'vue' import { computed, h, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NIcon, useThemeVars } from 'naive-ui' import { NButton, NIcon, 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'
@ -50,7 +49,7 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['loadmore', 'loadall', 'reload', 'rename', 'delete', 'match']) const emit = defineEmits(['loadmore', 'loadall', 'match'])
/** /**
* *
@ -177,18 +176,7 @@ defineExpose({
<template> <template>
<div class="content-wrapper flex-box-v"> <div class="content-wrapper flex-box-v">
<content-toolbar <slot name="toolbar" />
:db="props.db"
:key-code="props.keyCode"
:key-path="props.keyPath"
:key-type="keyType"
:loading="props.loading"
:server="props.name"
:ttl="ttl"
class="value-item-part"
@delete="emit('delete')"
@reload="emit('reload')"
@rename="emit('rename')" />
<div class="tb2 value-item-part flex-box-h"> <div class="tb2 value-item-part flex-box-h">
<div class="flex-box-h"> <div class="flex-box-h">
<content-search-input <content-search-input

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { computed, reactive, ref, watchEffect } from 'vue' import { computed, reactive, ref, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
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'
@ -34,8 +33,6 @@ const props = defineProps({
loading: Boolean, loading: Boolean,
}) })
const emit = defineEmits(['reload', 'rename', 'delete'])
const i18n = useI18n() const i18n = useI18n()
const themeVars = useThemeVars() const themeVars = useThemeVars()
const prefStore = usePreferencesStore() const prefStore = usePreferencesStore()
@ -172,18 +169,7 @@ defineExpose({
<template> <template>
<div class="content-wrapper flex-box-v"> <div class="content-wrapper flex-box-v">
<content-toolbar <slot name="toolbar" />
:db="props.db"
:key-code="keyCode"
:key-path="keyPath"
:key-type="keyType"
:loading="loading"
:server="props.name"
:ttl="ttl"
class="value-item-part"
@delete="emit('delete')"
@reload="emit('reload')"
@rename="emit('rename')" />
<div class="tb2 value-item-part flex-box-h"> <div class="tb2 value-item-part flex-box-h">
<div class="flex-item-expand"></div> <div class="flex-item-expand"></div>
<n-button-group> <n-button-group>

View File

@ -13,6 +13,7 @@ import { isEmpty } from 'lodash'
import useDialogStore from 'stores/dialog.js' import useDialogStore from 'stores/dialog.js'
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js' import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from '@/components/content_value/ContentToolbar.vue'
const themeVars = useThemeVars() const themeVars = useThemeVars()
const browserStore = useBrowserStore() const browserStore = useBrowserStore()
@ -42,6 +43,7 @@ const i18n = useI18n()
* format: String, * format: String,
* decode: String, * decode: String,
* end: Boolean * end: Boolean
* loading: Boolean
* }>} * }>}
*/ */
const data = computed(() => { const data = computed(() => {
@ -49,6 +51,10 @@ const data = computed(() => {
}) })
const initializing = ref(false) const initializing = ref(false)
const loading = computed(() => {
return data.value.loading === true || initializing.value
})
const binaryKey = computed(() => { const binaryKey = computed(() => {
return !!data.value.keyCode return !!data.value.keyCode
}) })
@ -188,7 +194,7 @@ watch(() => data.value?.keyPath, initContent)
:key-code="data.keyCode" :key-code="data.keyCode"
:key-path="data.keyPath" :key-path="data.keyPath"
:length="data.length" :length="data.length"
:loading="data.loading === true || initializing" :loading="loading"
:name="data.name" :name="data.name"
:size="data.size" :size="data.size"
:ttl="data.ttl" :ttl="data.ttl"
@ -197,8 +203,22 @@ watch(() => data.value?.keyPath, initContent)
@loadall="onLoadAll" @loadall="onLoadAll"
@loadmore="onLoadMore" @loadmore="onLoadMore"
@match="onMatch" @match="onMatch"
@reload="onReload" @reload="onReload">
@rename="onRename" /> <template #toolbar>
<content-toolbar
:db="data.db"
:key-code="data.keyCode"
:key-path="data.keyPath"
:key-type="data.type"
:loading="loading"
:server="data.name"
:ttl="data.ttl"
class="value-item-part"
@delete="onDelete"
@reload="onReload"
@rename="onRename" />
</template>
</component>
<!-- </keep-alive>--> <!-- </keep-alive>-->
</template> </template>

View File

@ -1,7 +1,6 @@
<script setup> <script setup>
import { computed, h, reactive, ref } from 'vue' import { computed, h, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ContentToolbar from './ContentToolbar.vue'
import AddLink from '@/components/icons/AddLink.vue' import AddLink from '@/components/icons/AddLink.vue'
import { NButton, NIcon, useThemeVars } from 'naive-ui' import { NButton, NIcon, 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'
@ -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', 'match'])
/** /**
* *
@ -141,6 +140,8 @@ const valueColumn = computed(() => ({
tooltip: { tooltip: {
style: { style: {
maxWidth: '80vw', maxWidth: '80vw',
maxHeight: '60vh',
overflowY: 'scroll',
}, },
}, },
}, },
@ -331,18 +332,7 @@ defineExpose({
<template> <template>
<div class="content-wrapper flex-box-v"> <div class="content-wrapper flex-box-v">
<content-toolbar <slot name="toolbar" />
:db="props.db"
:key-code="props.keyCode"
:key-path="props.keyPath"
:key-type="keyType"
:loading="props.loading"
:server="props.name"
:ttl="ttl"
class="value-item-part"
@delete="emit('delete')"
@reload="emit('reload')"
@rename="emit('rename')" />
<div class="tb2 value-item-part flex-box-h"> <div class="tb2 value-item-part flex-box-h">
<div class="flex-box-h"> <div class="flex-box-h">
<content-search-input <content-search-input

View File

@ -13,6 +13,7 @@ import { NSpace } from 'naive-ui'
import useTabStore from 'stores/tab.js' import useTabStore from 'stores/tab.js'
import NewStreamValue from '@/components/new_value/NewStreamValue.vue' import NewStreamValue from '@/components/new_value/NewStreamValue.vue'
import useBrowserStore from 'stores/browser.js' import useBrowserStore from 'stores/browser.js'
import Import from '@/components/icons/Import.vue'
const i18n = useI18n() const i18n = useI18n()
const newForm = reactive({ const newForm = reactive({
@ -64,6 +65,7 @@ const defaultValue = {
} }
const dialogStore = useDialog() const dialogStore = useDialog()
const scrollRef = ref(null)
watchEffect(() => { watchEffect(() => {
if (dialogStore.newKeyDialogVisible) { if (dialogStore.newKeyDialogVisible) {
const { prefix, server, db } = dialogStore.newKeyParam const { prefix, server, db } = dialogStore.newKeyParam
@ -106,6 +108,12 @@ const renderTypeLabel = (option) => {
) )
} }
const onAppend = () => {
nextTick(() => {
scrollRef.value?.scrollTo({ position: 'bottom' })
})
}
const onChangeType = () => { const onChangeType = () => {
newForm.value = null newForm.value = null
} }
@ -135,6 +143,7 @@ const onAdd = async () => {
if (value == null) { if (value == null) {
value = defaultValue[type] value = defaultValue[type]
} }
// await browserStore.reloadKey({server, db, key: trim(key)})
const { success, msg, nodeKey } = await browserStore.setKey({ const { success, msg, nodeKey } = await browserStore.setKey({
server, server,
db, db,
@ -147,7 +156,7 @@ const onAdd = async () => {
// select current key // select current key
await nextTick() await nextTick()
tabStore.setSelectedKeys(server, nodeKey) tabStore.setSelectedKeys(server, nodeKey)
browserStore.loadKeySummary({ server, db, key, clearValue: true }) browserStore.reloadKey({ server, db, key })
} else if (!isEmpty(msg)) { } else if (!isEmpty(msg)) {
$message.error(msg) $message.error(msg)
} }
@ -161,6 +170,11 @@ const onAdd = async () => {
const onClose = () => { const onClose = () => {
dialogStore.closeNewKeyDialog() dialogStore.closeNewKeyDialog()
} }
const onImport = () => {
dialogStore.closeNewKeyDialog()
dialogStore.openImportKeyDialog(newForm.server, newForm.db)
}
</script> </script>
<template> <template>
@ -169,18 +183,12 @@ const onClose = () => {
:closable="false" :closable="false"
:close-on-esc="false" :close-on-esc="false"
:mask-closable="false" :mask-closable="false"
:negative-button-props="{ size: 'medium' }"
:negative-text="$t('common.cancel')"
:positive-button-props="{ size: 'medium' }"
:positive-text="$t('common.confirm')"
:show-icon="false" :show-icon="false"
:title="$t('dialogue.key.new')" :title="$t('dialogue.key.new')"
preset="dialog" preset="dialog"
style="width: 600px" style="width: 600px"
transform-origin="center" transform-origin="center">
@positive-click="onAdd" <n-scrollbar ref="scrollRef" style="max-height: 500px">
@negative-click="onClose">
<n-scrollbar style="max-height: 500px">
<n-form <n-form
ref="newFormRef" ref="newFormRef"
:model="newForm" :model="newForm"
@ -218,10 +226,33 @@ const onClose = () => {
</n-button> </n-button>
</n-input-group> </n-input-group>
</n-form-item> </n-form-item>
<component :is="newValueComponent[newForm.type]" ref="subFormRef" v-model:value="newForm.value" /> <component
:is="newValueComponent[newForm.type]"
ref="subFormRef"
v-model:value="newForm.value"
@append="onAppend" />
<!-- TODO: Add import from txt file option --> <!-- TODO: Add import from txt file option -->
</n-form> </n-form>
</n-scrollbar> </n-scrollbar>
<template #action>
<div class="flex-item-expand">
<n-button :focusable="false" size="medium" @click="onImport">
<template #icon>
<n-icon :component="Import" />
</template>
{{ $t('interface.import_key') }}
</n-button>
</div>
<div class="flex-item n-dialog__action">
<n-button :focusable="false" size="medium" @click="onClose">
{{ $t('common.cancel') }}
</n-button>
<n-button :focusable="false" size="medium" type="primary" @click="onAdd">
{{ $t('common.confirm') }}
</n-button>
</div>
</template>
</n-modal> </n-modal>
</template> </template>

View File

@ -3,7 +3,7 @@ import { computed, reactive, ref, watchEffect } from 'vue'
import useDialog from 'stores/dialog' import useDialog from 'stores/dialog'
import useBrowserStore from 'stores/browser.js' import useBrowserStore from 'stores/browser.js'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { isEmpty, size } from 'lodash' import { isEmpty } from 'lodash'
import TtlInput from '@/components/common/TtlInput.vue' import TtlInput from '@/components/common/TtlInput.vue'
const ttlForm = reactive({ const ttlForm = reactive({

View File

@ -8,7 +8,7 @@ import IconButton from '@/components/common/IconButton.vue'
const props = defineProps({ const props = defineProps({
value: Array, value: Array,
}) })
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value', 'append'])
/** /**
* @typedef Hash * @typedef Hash
@ -46,7 +46,15 @@ defineExpose({
@update:value="onUpdate"> @update:value="onUpdate">
<template #action="{ index, create, remove, move }"> <template #action="{ index, create, remove, move }">
<icon-button v-if="kvList.length > 1" :icon="Delete" size="18" @click="() => remove(index)" /> <icon-button v-if="kvList.length > 1" :icon="Delete" size="18" @click="() => remove(index)" />
<icon-button :icon="Add" size="18" @click="() => create(index)" /> <icon-button
:icon="Add"
size="18"
@click="
() => {
create(index)
emit('append')
}
" />
</template> </template>
</n-dynamic-input> </n-dynamic-input>
</n-form-item> </n-form-item>

View File

@ -8,7 +8,7 @@ import IconButton from '@/components/common/IconButton.vue'
const props = defineProps({ const props = defineProps({
value: Array, value: Array,
}) })
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value', 'append'])
const list = ref(['']) const list = ref([''])
const onUpdate = (val) => { const onUpdate = (val) => {
@ -28,7 +28,15 @@ defineExpose({
<n-dynamic-input v-model:value="list" :placeholder="$t('dialogue.field.enter_elem')" @update:value="onUpdate"> <n-dynamic-input v-model:value="list" :placeholder="$t('dialogue.field.enter_elem')" @update:value="onUpdate">
<template #action="{ index, create, remove, move }"> <template #action="{ index, create, remove, move }">
<icon-button v-if="list.length > 1" :icon="Delete" size="18" @click="() => remove(index)" /> <icon-button v-if="list.length > 1" :icon="Delete" size="18" @click="() => remove(index)" />
<icon-button :icon="Add" size="18" @click="() => create(index)" /> <icon-button
:icon="Add"
size="18"
@click="
() => {
create(index)
emit('append')
}
" />
</template> </template>
</n-dynamic-input> </n-dynamic-input>
</n-form-item> </n-form-item>

View File

@ -8,7 +8,7 @@ import IconButton from '@/components/common/IconButton.vue'
const props = defineProps({ const props = defineProps({
value: Array, value: Array,
}) })
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value', 'append'])
const set = ref(['']) const set = ref([''])
const onUpdate = (val) => { const onUpdate = (val) => {
@ -28,7 +28,15 @@ defineExpose({
<n-dynamic-input v-model:value="set" :placeholder="$t('dialogue.field.enter_elem')" @update:value="onUpdate"> <n-dynamic-input v-model:value="set" :placeholder="$t('dialogue.field.enter_elem')" @update:value="onUpdate">
<template #action="{ index, create, remove, move }"> <template #action="{ index, create, remove, move }">
<icon-button v-if="set.length > 1" :icon="Delete" size="18" @click="() => remove(index)" /> <icon-button v-if="set.length > 1" :icon="Delete" size="18" @click="() => remove(index)" />
<icon-button :icon="Add" size="18" @click="() => create(index)" /> <icon-button
:icon="Add"
size="18"
@click="
() => {
create(index)
emit('append')
}
" />
</template> </template>
</n-dynamic-input> </n-dynamic-input>
</n-form-item> </n-form-item>

View File

@ -12,7 +12,7 @@ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })
const id = ref('*') const id = ref('*')
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value', 'append'])
/** /**
* @typedef Hash * @typedef Hash
@ -52,7 +52,15 @@ defineExpose({
@update:value="onUpdate"> @update:value="onUpdate">
<template #action="{ index, create, remove, move }"> <template #action="{ index, create, remove, move }">
<icon-button v-if="kvList.length > 1" :icon="Delete" size="18" @click="() => remove(index)" /> <icon-button v-if="kvList.length > 1" :icon="Delete" size="18" @click="() => remove(index)" />
<icon-button :icon="Add" size="18" @click="() => create(index)" /> <icon-button
:icon="Add"
size="18"
@click="
() => {
create(index)
emit('append')
}
" />
</template> </template>
</n-dynamic-input> </n-dynamic-input>
</n-form-item> </n-form-item>

View File

@ -8,7 +8,7 @@ import IconButton from '@/components/common/IconButton.vue'
const props = defineProps({ const props = defineProps({
value: Array, value: Array,
}) })
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value', 'append'])
/** /**
* @typedef ZSetItem * @typedef ZSetItem
@ -57,7 +57,15 @@ defineExpose({
</template> </template>
<template #action="{ index, create, remove, move }"> <template #action="{ index, create, remove, move }">
<icon-button v-if="zset.length > 1" :icon="Delete" size="18" @click="() => remove(index)" /> <icon-button v-if="zset.length > 1" :icon="Delete" size="18" @click="() => remove(index)" />
<icon-button :icon="Add" size="18" @click="() => create(index)" /> <icon-button
:icon="Add"
size="18"
@click="
() => {
create(index)
emit('append')
}
" />
</template> </template>
</n-dynamic-input> </n-dynamic-input>
</n-form-item> </n-form-item>

View File

@ -25,7 +25,6 @@ import More from '@/components/icons/More.vue'
import Export from '@/components/icons/Export.vue' import Export from '@/components/icons/Export.vue'
import { ConnectionType } from '@/consts/connection_type.js' import { ConnectionType } from '@/consts/connection_type.js'
import Import from '@/components/icons/Import.vue' import Import from '@/components/icons/Import.vue'
import Down from '@/components/icons/Down.vue'
import Checkbox from '@/components/icons/Checkbox.vue' import Checkbox from '@/components/icons/Checkbox.vue'
import Timer from '@/components/icons/Timer.vue' import Timer from '@/components/icons/Timer.vue'
@ -65,12 +64,8 @@ const dbSelectOptions = computed(() => {
}) })
}) })
const addOptions = computed(() => [
{ key: 'import', label: i18n.t('interface.import_key'), icon: render.renderIcon(Import, { strokeWidth: 3.5 }) },
])
const moreOptions = computed(() => [ const moreOptions = computed(() => [
// { key: 'import', label: i18n.t('interface.import_key'), icon: render.renderIcon(Import, { strokeWidth: 3.5 }) }, { key: 'import', label: i18n.t('interface.import_key'), icon: render.renderIcon(Import, { strokeWidth: 3.5 }) },
{ key: 'flush', label: i18n.t('interface.flush_db'), icon: render.renderIcon(Delete, { strokeWidth: 3.5 }) }, { key: 'flush', label: i18n.t('interface.flush_db'), icon: render.renderIcon(Delete, { strokeWidth: 3.5 }) },
{ key: 'divider', type: 'divider' }, { key: 'divider', type: 'divider' },
{ {
@ -101,10 +96,9 @@ const checkedTip = computed(() => {
const onReload = async () => { const onReload = async () => {
try { try {
loading.value = true loading.value = true
tabStore.setSelectedKeys(props.server) // tabStore.setSelectedKeys(props.server)
const db = props.db const db = props.db
browserStore.closeDatabase(props.server, db) browserStore.closeDatabase(props.server, db)
// browserTreeRef.value?.resetExpandKey(props.server, db)
let matchType = unref(filterForm.type) let matchType = unref(filterForm.type)
if (!types.hasOwnProperty(matchType)) { if (!types.hasOwnProperty(matchType)) {
@ -200,7 +194,6 @@ const handleSelectDB = async (db) => {
await nextTick() await nextTick()
await connectionStore.saveLastDB(props.server, db) await connectionStore.saveLastDB(props.server, db)
tabStore.upsertTab({ server: props.server, db, clearValue: true }) tabStore.upsertTab({ server: props.server, db, clearValue: true })
// browserTreeRef.value?.resetExpandKey(props.server, db)
fullyLoaded.value = await browserStore.loadMoreKeys(props.server, db) fullyLoaded.value = await browserStore.loadMoreKeys(props.server, db)
browserTreeRef.value?.refreshTree() browserTreeRef.value?.refreshTree()
tabStore.setSelectedKeys(props.server) tabStore.setSelectedKeys(props.server)
@ -273,16 +266,16 @@ watch(
<redis-type-selector v-model:value="filterForm.type" @update:value="onSelectFilterType" /> <redis-type-selector v-model:value="filterForm.type" @update:value="onSelectFilterType" />
</template> </template>
</content-search-input> </content-search-input>
<icon-button
:disabled="loading"
:icon="Refresh"
border
size="18"
small
stroke-width="4"
t-tooltip="interface.reload"
@click="onReload" />
<n-button-group> <n-button-group>
<icon-button
:disabled="loading"
:icon="Refresh"
border
size="18"
small
stroke-width="4"
t-tooltip="interface.reload"
@click="onReload" />
<icon-button <icon-button
:disabled="loading" :disabled="loading"
:icon="Plus" :icon="Plus"
@ -292,18 +285,6 @@ watch(
stroke-width="4" stroke-width="4"
t-tooltip="interface.new_key" t-tooltip="interface.new_key"
@click="onAddKey" /> @click="onAddKey" />
<n-dropdown
:options="addOptions"
placement="bottom-end"
style="min-width: 130px"
trigger="click"
@select="onSelectOptions">
<n-button :focusable="false" size="small" style="padding: 0 3px">
<n-icon size="10">
<down :stroke-width="6" />
</n-icon>
</n-button>
</n-dropdown>
</n-button-group> </n-button-group>
</div> </div>

View File

@ -1,11 +1,11 @@
<script setup> <script setup>
import { computed, h, nextTick, reactive, ref, watchEffect } from 'vue' import { computed, h, nextTick, reactive, ref, watchEffect } from 'vue'
import { ConnectionType } from '@/consts/connection_type.js' import { ConnectionType } from '@/consts/connection_type.js'
import { NIcon, NSpace, useThemeVars } from 'naive-ui' import { NIcon, NSpace, NText, useThemeVars } from 'naive-ui'
import Key from '@/components/icons/Key.vue' import Key from '@/components/icons/Key.vue'
import Binary from '@/components/icons/Binary.vue' import Binary from '@/components/icons/Binary.vue'
import Database from '@/components/icons/Database.vue' import Database from '@/components/icons/Database.vue'
import { filter, find, get, includes, indexOf, isEmpty, map, remove, size, startsWith, toUpper } from 'lodash' import { filter, find, get, includes, isEmpty, map, size, toUpper } from 'lodash'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Refresh from '@/components/icons/Refresh.vue' import Refresh from '@/components/icons/Refresh.vue'
import CopyLink from '@/components/icons/CopyLink.vue' import CopyLink from '@/components/icons/CopyLink.vue'
@ -40,14 +40,24 @@ const props = defineProps({
const themeVars = useThemeVars() const themeVars = useThemeVars()
const render = useRender() const render = useRender()
const i18n = useI18n() const i18n = useI18n()
/** @type {Ref<UnwrapRef<string[]>>} */
const expandedKeys = ref([])
const connectionStore = useConnectionStore() const connectionStore = useConnectionStore()
const browserStore = useBrowserStore() const browserStore = useBrowserStore()
const prefStore = usePreferencesStore() const prefStore = usePreferencesStore()
const tabStore = useTabStore() const tabStore = useTabStore()
const dialogStore = useDialogStore() const dialogStore = useDialogStore()
/**
*
* @type {ComputedRef<string[]>}
*/
const expandedKeys = computed(() => {
const tab = find(tabStore.tabList, { name: props.server })
if (tab != null) {
return get(tab, 'expandedKeys', [])
}
return []
})
/** /**
* *
* @type {ComputedRef<string[]>} * @type {ComputedRef<string[]>}
@ -168,31 +178,6 @@ const renderContextLabel = (option) => {
return h('div', { class: 'context-menu-item' }, option.label) return h('div', { class: 'context-menu-item' }, option.label)
} }
/**
*
* @param {string} key
* @param {boolean} [toggle]
*/
const expandKey = (key, toggle) => {
const idx = indexOf(expandedKeys.value, key)
if (idx === -1) {
expandedKeys.value.push(key)
} else if (toggle !== false) {
expandedKeys.value.splice(idx, 1)
}
}
const resetExpandKey = (server, db, includeDB) => {
const prefix = `${server}/db${db}`
remove(expandedKeys.value, (k) => {
if (!!!includeDB) {
return k !== prefix && startsWith(k, prefix)
} else {
return startsWith(k, prefix)
}
})
}
const handleSelectContextMenu = (key) => { const handleSelectContextMenu = (key) => {
contextMenuParam.show = false contextMenuParam.show = false
const selectedKey = get(selectedKeys.value, 0) const selectedKey = get(selectedKeys.value, 0)
@ -291,14 +276,14 @@ const onUpdateSelectedKeys = (keys, options) => {
} }
} }
// default is load blank key to display server status // default is load blank key to display server status
tabStore.openBlank(props.server, props.db) // tabStore.openBlank(props.server)
} finally { } finally {
tabStore.setSelectedKeys(props.server, keys) tabStore.setSelectedKeys(props.server, keys)
} }
} }
const onUpdateExpanded = (value, option, meta) => { const onUpdateExpanded = (value, option, meta) => {
expandedKeys.value = value tabStore.setExpandedKeys(props.server, value)
if (!meta.node) { if (!meta.node) {
return return
} }
@ -310,13 +295,13 @@ const onUpdateExpanded = (value, option, meta) => {
switch (meta.action) { switch (meta.action) {
case 'expand': case 'expand':
node.expanded = true node.expanded = true
if (!includes(expandedKeys.value, key)) { if (!includes(value, key)) {
expandedKeys.value.push(key) tabStore.addExpandedKey(props.server, key)
} }
break break
case 'collapse': case 'collapse':
node.expanded = false node.expanded = false
remove(expandedKeys.value, (v) => v === key) tabStore.removeExpandedKey(props.server, key)
break break
} }
node = node.children[0] node = node.children[0]
@ -374,8 +359,7 @@ const renderPrefix = ({ option }) => {
browserStore.loadKeyType({ browserStore.loadKeyType({
server: props.server, server: props.server,
db: option.db, db: option.db,
key: option.redisKey, key: option.redisKeyCode || option.redisKey,
keyCode: option.redisKeyCode,
}) })
} }
switch (prefStore.keyIconType) { switch (prefStore.keyIconType) {
@ -412,6 +396,13 @@ const renderPrefix = ({ option }) => {
const renderLabel = ({ option }) => { const renderLabel = ({ option }) => {
switch (option.type) { switch (option.type) {
case ConnectionType.RedisKey: case ConnectionType.RedisKey:
if (option.label === '') {
// blank label name
return h('div', [
h(NText, { italic: true, depth: 3 }, () => '[Empty]'),
h('span', () => ` (${option.keyCount || 0})`),
])
}
return `${option.label} (${option.keyCount || 0})` return `${option.label} (${option.keyCount || 0})`
// case ConnectionType.RedisValue: // case ConnectionType.RedisValue:
// return `[${option.keyType}]${option.label}` // return `[${option.keyType}]${option.label}`
@ -525,8 +516,8 @@ const nodeProps = ({ option }) => {
return return
} }
if (!props.checkMode) { if (!props.checkMode) {
// default handle is expand current node // default handle is toggle expand current node
nextTick().then(() => expandKey(option.key)) nextTick().then(() => tabStore.toggleExpandKey(props.server, option.key))
} }
}, },
onContextmenu(e) { onContextmenu(e) {
@ -558,7 +549,7 @@ watchEffect(
if (!props.checkMode) { if (!props.checkMode) {
tabStore.setCheckedKeys(props.server) tabStore.setCheckedKeys(props.server)
} else { } else {
expandKey(`${ConnectionType.RedisDB}`, false) tabStore.addExpandedKey(props.server, `${ConnectionType.RedisDB}`)
} }
}, },
{ flush: 'post' }, { flush: 'post' },
@ -569,10 +560,9 @@ watchEffect(
const treeKey = ref(0) const treeKey = ref(0)
defineExpose({ defineExpose({
handleSelectContextMenu, handleSelectContextMenu,
resetExpandKey,
refreshTree: () => { refreshTree: () => {
treeKey.value = Date.now() treeKey.value = Date.now()
expandedKeys.value = [] tabStore.setExpandedKeys(props.server)
tabStore.setCheckedKeys(props.server) tabStore.setCheckedKeys(props.server)
}, },
deleteCheckedItems: () => { deleteCheckedItems: () => {

View File

@ -246,12 +246,12 @@ const getServerMenu = (connected) => {
const getGroupMenu = () => { const getGroupMenu = () => {
return [ return [
h(IconButton, { h(IconButton, {
tTooltip: 'interface.edit_conn', tTooltip: 'interface.rename_conn_group',
icon: Config, icon: Config,
onClick: () => handleSelectContextMenu('group_rename'), onClick: () => handleSelectContextMenu('group_rename'),
}), }),
h(IconButton, { h(IconButton, {
tTooltip: 'interface.remove_conn', tTooltip: 'interface.remove_conn_group',
icon: Delete, icon: Delete,
onClick: () => handleSelectContextMenu('group_delete'), onClick: () => handleSelectContextMenu('group_delete'),
}), }),

View File

@ -13,9 +13,10 @@
"minute": "Minutes(s)", "minute": "Minutes(s)",
"hour": "Hour(s)", "hour": "Hour(s)",
"day": "Day(s)", "day": "Day(s)",
"unit_day": "D", "unit_day": "d",
"unit_hour": "H", "unit_hour": "h",
"unit_minute": "M", "unit_minute": "m",
"unit_second": "s",
"all": "All", "all": "All",
"key": "Key", "key": "Key",
"value": "Value", "value": "Value",
@ -70,15 +71,15 @@
"dup_conn": "Duplicate Connection", "dup_conn": "Duplicate Connection",
"remove_conn": "Delete Connection", "remove_conn": "Delete Connection",
"edit_conn": "Edit Connection Config", "edit_conn": "Edit Connection Config",
"edit_conn_group": "Edit Connection Group", "edit_conn_group": "Edit Group",
"rename_conn_group": "Rename Connection Group", "rename_conn_group": "Rename Group",
"remove_conn_group": "Delete Connection Group", "remove_conn_group": "Delete Group",
"ttl": "TTL", "ttl": "TTL",
"forever": "Forever", "forever": "Forever",
"rename_key": "Rename Key", "rename_key": "Rename Key",
"delete_key": "Delete Key", "delete_key": "Delete Key",
"batch_delete_key": "Batch Delete Keys", "batch_delete_key": "Batch Delete Keys",
"import_key": "Import Key", "import_key": "Import Key(s)",
"flush_db": "Flush Database", "flush_db": "Flush Database",
"check_mode": "Check Mode", "check_mode": "Check Mode",
"quit_check_mode": "Quit Check Mode", "quit_check_mode": "Quit Check Mode",
@ -108,6 +109,8 @@
"decode_with": "Decode / Decompression", "decode_with": "Decode / Decompression",
"reload": "Reload", "reload": "Reload",
"reload_disable": "Reload will enable after full loaded", "reload_disable": "Reload will enable after full loaded",
"auto_refresh": "Auto Refresh",
"refresh_interval": "Refresh Interval",
"open_connection": "Open Connection", "open_connection": "Open Connection",
"copy_path": "Copy Path", "copy_path": "Copy Path",
"copy_key": "Copy Key", "copy_key": "Copy Key",

View File

@ -10,12 +10,13 @@
"update": "更新", "update": "更新",
"none": "无", "none": "无",
"second": "秒", "second": "秒",
"minute": "分", "minute": "分",
"hour": "小时", "hour": "小时",
"day": "天", "day": "天",
"unit_day": "天", "unit_day": "天",
"unit_hour": "小时", "unit_hour": "小时",
"unit_minute": "分钟", "unit_minute": "分钟",
"unit_second": "秒",
"all": "全部", "all": "全部",
"key": "键", "key": "键",
"value": "值", "value": "值",
@ -70,9 +71,9 @@
"dup_conn": "复制连接", "dup_conn": "复制连接",
"remove_conn": "删除连接", "remove_conn": "删除连接",
"edit_conn": "编辑连接配置", "edit_conn": "编辑连接配置",
"edit_conn_group": "编辑连接分组", "edit_conn_group": "编辑分组",
"rename_conn_group": "重命名连接分组", "rename_conn_group": "重命名分组",
"remove_conn_group": "删除连接分组", "remove_conn_group": "删除分组",
"ttl": "TTL", "ttl": "TTL",
"forever": "永久", "forever": "永久",
"rename_key": "重命名键", "rename_key": "重命名键",
@ -108,6 +109,8 @@
"decode_with": "解码/解压方式", "decode_with": "解码/解压方式",
"reload": "重新载入", "reload": "重新载入",
"reload_disable": "全量加载后可重新载入", "reload_disable": "全量加载后可重新载入",
"auto_refresh": "自动刷新",
"refresh_interval": "刷新间隔",
"open_connection": "打开连接", "open_connection": "打开连接",
"copy_path": "复制路径", "copy_path": "复制路径",
"copy_key": "复制键名", "copy_key": "复制键名",

View File

@ -52,7 +52,7 @@ export class RedisServerState {
this.getRoot() this.getRoot()
const connStore = useConnectionStore() const connStore = useConnectionStore()
const { keySeparator } = connStore.getDefaultSeparator(name) const keySeparator = connStore.getDefaultSeparator(name)
this.separator = isEmpty(keySeparator) ? ':' : keySeparator this.separator = isEmpty(keySeparator) ? ':' : keySeparator
} }
@ -246,8 +246,8 @@ export class RedisServerState {
/** /**
* rename key to a new name * rename key to a new name
* @param key * @param {string} key
* @param newKey * @param {string} newKey
*/ */
renameKey(key, newKey) { renameKey(key, newKey) {
const oldLayer = initial(key.split(this.separator)).join(this.separator) const oldLayer = initial(key.split(this.separator)).join(this.separator)
@ -265,7 +265,11 @@ export class RedisServerState {
const newNodeKeyName = `${ConnectionType.RedisValue}/${newKey}` const newNodeKeyName = `${ConnectionType.RedisValue}/${newKey}`
const keyNode = this.nodeMap.get(oldNodeKeyName) const keyNode = this.nodeMap.get(oldNodeKeyName)
keyNode.key = `${this.name}/db${this.db}#${newNodeKeyName}` keyNode.key = `${this.name}/db${this.db}#${newNodeKeyName}`
keyNode.label = last(split(newKey, this.separator)) if (this.viewType === KeyViewType.Tree) {
keyNode.label = last(split(newKey, this.separator))
} else {
keyNode.label = newKey
}
keyNode.redisKey = newKey keyNode.redisKey = newKey
// not support rename binary key name yet // not support rename binary key name yet
// keyNode.redisKeyCode = [] // keyNode.redisKeyCode = []

View File

@ -16,6 +16,7 @@ export class TabItem {
* @param {string} subTab secondary tab value * @param {string} subTab secondary tab value
* @param {string} [title] tab title * @param {string} [title] tab title
* @param {string} [icon] tab icon * @param {string} [icon] tab icon
* @param {string[]} expandedKeys
* @param {string[]} selectedKeys * @param {string[]} selectedKeys
* @param {CheckedKey[]} checkedKeys * @param {CheckedKey[]} checkedKeys
* @param {string} [type] key type * @param {string} [type] key type
@ -39,8 +40,9 @@ export class TabItem {
blank, blank,
subTab, subTab,
icon, icon,
selectedKeys, expandedKeys = [],
checkedKeys, selectedKeys = [],
checkedKeys = [],
type, type,
value, value,
server, server,
@ -61,6 +63,7 @@ export class TabItem {
this.blank = blank this.blank = blank
this.subTab = subTab this.subTab = subTab
this.icon = icon this.icon = icon
this.expandedKeys = expandedKeys
this.selectedKeys = selectedKeys this.selectedKeys = selectedKeys
this.checkedKeys = checkedKeys this.checkedKeys = checkedKeys
this.type = type this.type = type

View File

@ -245,6 +245,7 @@ const useBrowserStore = defineStore('browser', {
name, name,
separator: this.getSeparator(name), separator: this.getSeparator(name),
db: -1, db: -1,
viewType: view,
}) })
/** @type {Object.<number,RedisDatabaseItem>} **/ /** @type {Object.<number,RedisDatabaseItem>} **/
const databases = {} const databases = {}
@ -379,7 +380,7 @@ const useBrowserStore = defineStore('browser', {
}) })
if (success) { if (success) {
const { type, ttl, size, length } = data const { type, ttl, size, length } = data
const k = decodeRedisKey(key) const k = nativeRedisKey(key)
const binaryKey = k !== key const binaryKey = k !== key
tab.upsertTab({ tab.upsertTab({
subTab: BrowserTabType.KeyDetail, subTab: BrowserTabType.KeyDetail,
@ -426,23 +427,23 @@ const useBrowserStore = defineStore('browser', {
* load key type * load key type
* @param {string} server * @param {string} server
* @param {number} db * @param {number} db
* @param {string} key * @param {string|number[]} key
* @param {number[]} keyCode * @param {number[]} keyCode
* @return {Promise<void>} * @return {Promise<void>}
*/ */
async loadKeyType({ server, db, key, keyCode }) { async loadKeyType({ server, db, key }) {
/** @type {RedisServerState} **/ /** @type {RedisServerState} **/
const serverInst = this.servers[server] const serverInst = this.servers[server]
if (serverInst == null) { if (serverInst == null) {
return return
} }
const node = serverInst.getNode(ConnectionType.RedisValue, key) const node = serverInst.getNode(ConnectionType.RedisValue, nativeRedisKey(key))
if (node == null || !isEmpty(node.redisType)) { if (node == null || !isEmpty(node.redisType)) {
return return
} }
try { try {
node.redisType = 'loading' node.redisType = 'loading'
const { data, success } = await GetKeyType({ server, db, key: keyCode || key }) const { data, success, msg } = await GetKeyType({ server, db, key })
if (success) { if (success) {
const { type } = data || {} const { type } = data || {}
node.redisType = type node.redisType = type
@ -504,11 +505,19 @@ 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,
keyType,
decode: retDecode,
format: retFormat,
match: retMatch,
reset: retReset,
end,
} = data
tab.updateValue({ tab.updateValue({
server, server,
db, db,
key: decodeRedisKey(key), key: nativeRedisKey(key),
value, value,
decode: retDecode, decode: retDecode,
format: retFormat, format: retFormat,
@ -757,10 +766,10 @@ const useBrowserStore = defineStore('browser', {
serverInst.updateDBKeyCount(db, newKey) serverInst.updateDBKeyCount(db, newKey)
} }
} }
const tab = useTabStore() // const tab = useTabStore()
tab.updateValue({ server, db, key, value }) // tab.updateValue({ server, db, key, value })
this.loadKeySummary({ server, db, key }) // this.loadKeySummary({ server, db, key })
return { return {
success, success,
nodeKey: `${server}/db${db}#${ConnectionType.RedisValue}/${key}`, nodeKey: `${server}/db${db}#${ConnectionType.RedisValue}/${key}`,
@ -1497,7 +1506,7 @@ const useBrowserStore = defineStore('browser', {
tabStore.updateTTL({ tabStore.updateTTL({
server, server,
db, db,
key, key: nativeRedisKey(key),
ttl, ttl,
}) })
} }
@ -1806,6 +1815,7 @@ const useBrowserStore = defineStore('browser', {
tab.emptyTab(server) tab.emptyTab(server)
tab.setSelectedKeys(server) tab.setSelectedKeys(server)
tab.setCheckedKeys(server) tab.setCheckedKeys(server)
tab.setExpandedKeys(server)
return true return true
} }
} finally { } finally {

View File

@ -364,8 +364,8 @@ const useConnectionStore = defineStore('connections', {
* @return {string} * @return {string}
*/ */
getDefaultSeparator(name) { getDefaultSeparator(name) {
const { defaultSeparator = ':' } = this.serverProfile[name] || {} const { keySeparator = ':' } = this.serverProfile[name] || {}
return defaultSeparator return keySeparator
}, },
}, },
}) })

View File

@ -1,7 +1,6 @@
import { assign, find, findIndex, get, isEmpty, pullAt, remove, set, size } from 'lodash' import { assign, find, findIndex, get, indexOf, isEmpty, pullAt, remove, set, size } from 'lodash'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { TabItem } from '@/objects/tabItem.js' import { TabItem } from '@/objects/tabItem.js'
import { decodeRedisKey } from '@/utils/key_convert.js'
const useTabStore = defineStore('tab', { const useTabStore = defineStore('tab', {
/** /**
@ -543,11 +542,11 @@ const useTabStore = defineStore('tab', {
* update ttl in tab * update ttl in tab
* @param {string} server * @param {string} server
* @param {number} db * @param {number} db
* @param {string|number[]} key * @param {string} key
* @param {number} ttl * @param {number} ttl
*/ */
updateTTL({ server, db, key, ttl }) { updateTTL({ server, db, key, ttl }) {
let tab = find(this.tabList, { name: server, db, key: decodeRedisKey(key) }) let tab = find(this.tabList, { name: server, db, key })
if (tab == null) { if (tab == null) {
return return
} }
@ -638,11 +637,73 @@ const useTabStore = defineStore('tab', {
}, },
/** /**
* set selected keys in current display browser tree * set expanded keys for server
* @param {string} server
* @param {string[]} keys
*/
setExpandedKeys(server, keys = []) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
if (isEmpty(keys)) {
tab.expandedKeys = []
} else {
tab.expandedKeys = keys
}
}
},
/**
*
* @param {string} server
* @param {string} key
*/
addExpandedKey(server, key) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
tab.expandedKeys.push(key)
}
},
/**
*
* @param {string} server
* @param {string} key
*/
toggleExpandKey(server, key) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
const idx = indexOf(tab.expandedKeys, key)
if (idx === -1) {
tab.expandedKeys.push(key)
} else {
tab.expandedKeys.splice(idx, 1)
}
}
},
/**
*
* @param {string} server
* @param {string} key
*/
removeExpandedKey(server, key) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server })
if (tab != null) {
remove(tab.expandedKeys, (v) => v === key)
}
},
/**
* set selected keys for server
* @param {string} server * @param {string} server
* @param {string|string[]} [keys] * @param {string|string[]} [keys]
*/ */
setSelectedKeys(server, keys = null) { setSelectedKeys(server, keys = null) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server }) let tab = find(this.tabList, { name: server })
if (tab != null) { if (tab != null) {
if (keys == null) { if (keys == null) {
@ -662,6 +723,7 @@ const useTabStore = defineStore('tab', {
* @returns {CheckedKey[]} * @returns {CheckedKey[]}
*/ */
getCheckedKeys(server) { getCheckedKeys(server) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server }) let tab = find(this.tabList, { name: server })
if (tab != null) { if (tab != null) {
return tab.checkedKeys || [] return tab.checkedKeys || []
@ -670,11 +732,12 @@ const useTabStore = defineStore('tab', {
}, },
/** /**
* set checked keys in current display browser tree * set checked keys for server
* @param {string} server * @param {string} server
* @param {CheckedKey[]} [keys] * @param {CheckedKey[]} [keys]
*/ */
setCheckedKeys(server, keys = null) { setCheckedKeys(server, keys = null) {
/** @type TabItem**/
let tab = find(this.tabList, { name: server }) let tab = find(this.tabList, { name: server })
if (tab != null) { if (tab != null) {
if (isEmpty(keys)) { if (isEmpty(keys)) {

View File

@ -91,6 +91,9 @@ const _darkThemeOverrides = {
Dropdown: { Dropdown: {
color: '#272727', color: '#272727',
}, },
Popover: {
color: '#2C2C32',
},
} }
export const darkThemeOverrides = merge({}, themeOverrides, _darkThemeOverrides) export const darkThemeOverrides = merge({}, themeOverrides, _darkThemeOverrides)