From 261a7de2cf15ff00a38c74860e34069aa2b4309f Mon Sep 17 00:00:00 2001 From: qii404 Date: Mon, 24 Jan 2022 21:34:03 +0800 Subject: [PATCH] Feature/load all keys (#770) Load all keys support! due to conflict, can only merge via "squash and merge" Co-authored-by: Ricardo Tondello --- README.md | 2 + pack/electron/package.json | 2 +- package-lock.json | 16 +++--- package.json | 2 +- src/App.vue | 2 +- src/components/DeleteBatch.vue | 2 +- src/components/FormatViewer.vue | 14 +++-- src/components/JsonEditor.vue | 30 +++++----- src/components/KeyContentHash.vue | 1 + src/components/KeyContentList.vue | 1 + src/components/KeyContentReJson.vue | 12 +--- src/components/KeyContentSet.vue | 1 + src/components/KeyContentStream.vue | 5 +- src/components/KeyContentString.vue | 12 +--- src/components/KeyContentZset.vue | 1 + src/components/KeyList.vue | 85 ++++++++++++++++++++++------- src/components/KeyListTree.vue | 66 +++++++++++++++++----- src/components/OperateItem.vue | 2 +- src/components/ScrollToTop.vue | 24 +++----- src/components/Setting.vue | 7 ++- src/components/ViewerBinary.vue | 4 +- src/components/ViewerHex.vue | 4 +- src/components/ViewerOverSize.vue | 4 +- src/components/ViewerText.vue | 4 +- src/i18n/langs/cn.js | 4 ++ src/i18n/langs/de.js | 4 ++ src/i18n/langs/en.js | 4 ++ src/i18n/langs/fr.js | 4 ++ src/i18n/langs/it.js | 6 +- src/i18n/langs/pt.js | 6 +- src/i18n/langs/ru.js | 4 ++ src/i18n/langs/tr.js | 4 ++ src/i18n/langs/tw.js | 4 ++ src/i18n/langs/ua.js | 4 ++ src/util.js | 41 ++++++++++---- 35 files changed, 261 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index bb0ca23..7078bc0 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ ## Feature Log +- 2022-01-24: Command Dump Support +- 2022-01-05: Support To Load All Keys - 2022-01-01: Brotli\Gzip\Deflate Support && RedisJSON Support - 2021-11-26: JSON Editable && Subscribe Support - 2021-08-30: Execution log Support && Add Hot Keys diff --git a/pack/electron/package.json b/pack/electron/package.json index 85fff04..980a1de 100644 --- a/pack/electron/package.json +++ b/pack/electron/package.json @@ -1,6 +1,6 @@ { "name": "another-redis-desktop-manager", - "version": "1.5.1", + "version": "1.5.2", "description": "A faster, better and more stable redis desktop manager.", "author": "Another", "private": true, diff --git a/package-lock.json b/package-lock.json index f39f100..6098cef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -322,6 +322,14 @@ "resolved": "https://registry.npmjs.org/@qii404/redis-splitargs/-/redis-splitargs-1.0.1.tgz", "integrity": "sha512-7LvTGKoW0cvTNUQwzb+byHr4PBvlqizMumAiuR0GzASGV+BO8LQuMd6EW8ukmB6R/bQJq6zINyXtq1Oa3/mKBA==" }, + "@qii404/ztree_v3": { + "version": "3.5.48", + "resolved": "https://registry.npmjs.org/@qii404/ztree_v3/-/ztree_v3-3.5.48.tgz", + "integrity": "sha512-eKAQG88dkOwae3nvWI4m05P9a7eDZ0oyHCwz20UO2zXNsFfDiasvfYwCceytSY37U8MIL9iCuRuVN6UeC2duUQ==", + "requires": { + "jquery": ">=1.4.4" + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npm.taobao.org/@sindresorhus/is/download/@sindresorhus/is-0.14.0.tgz", @@ -726,14 +734,6 @@ "integrity": "sha1-0pHGpOl5ibXGHZrPOWrk/hM6cY0=", "dev": true }, - "@ztree/ztree_v3": { - "version": "3.5.46", - "resolved": "https://registry.npm.taobao.org/@ztree/ztree_v3/download/@ztree/ztree_v3-3.5.46.tgz", - "integrity": "sha1-rt8tzz+ZlVD/evZ4b32sm4CgCQA=", - "requires": { - "jquery": ">=1.4.4" - } - }, "abbrev": { "version": "1.1.1", "resolved": "http://registry.npm.taobao.org/abbrev/download/abbrev-1.1.1.tgz", diff --git a/package.json b/package.json index 21e20bf..cf87f8d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@msgpack/msgpack": "^2.3.0", "@qii404/json-bigint": "^1.0.0", "@qii404/redis-splitargs": "^1.0.1", - "@ztree/ztree_v3": "^3.5.46", + "@qii404/ztree_v3": "^3.5.48", "element-ui": "^2.4.11", "font-awesome": "^4.7.0", "ioredis": "^4.27.0", diff --git a/src/App.vue b/src/App.vue index 3df5786..cbdb3c5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -195,7 +195,7 @@ button, input, textarea, .vjs__tree { } .right-main-container .main-tabs-container { overflow-y: hidden; - padding-top: 2px; + padding-top: 0px; padding-right: 4px; } diff --git a/src/components/DeleteBatch.vue b/src/components/DeleteBatch.vue index d657908..b0d2da9 100644 --- a/src/components/DeleteBatch.vue +++ b/src/components/DeleteBatch.vue @@ -79,7 +79,7 @@ export default { nodes.map(node => { let scanOption = { match: pattern + '*', - count: 7000, + count: 50000, } let stream = node.scanBufferStream(scanOption); diff --git a/src/components/FormatViewer.vue b/src/components/FormatViewer.vue index 9068d16..af10e94 100644 --- a/src/components/FormatViewer.vue +++ b/src/components/FormatViewer.vue @@ -28,7 +28,6 @@ :content='content' :name="selectedView" :contentVisible='contentVisible' - :textrows='textrows' :disabled='disabled' :redisKey="redisKey" :dataMap="dataMap"> @@ -77,7 +76,6 @@ export default { props: { float: {default: 'right'}, content: {default: () => Buffer.from('')}, - textrows: {default: 6}, disabled: {type: Boolean, default: false}, redisKey: {default: () => Buffer.from('')}, dataMap: {type: Object, default: () => {}}, @@ -222,17 +220,21 @@ export default { height: 22px !important; } + /*outline same with text viewer's .el-textarea__inner*/ .text-formated-container { border: 1px solid #dcdfe6; - min-height: 114px; - padding: 5px 15px; - line-height: 1.5; - border-radius: 5px; + padding: 5px 10px; + border-radius: 4px; } .dark-mode .text-formated-container { border-color: #7f8ea5; } + .format-viewer-container textarea { + min-height: 194px !important; + height: calc(100vh - 686px); + } + .collapse-container { height: 27px; } diff --git a/src/components/JsonEditor.vue b/src/components/JsonEditor.vue index 8c369e2..7facc40 100644 --- a/src/components/JsonEditor.vue +++ b/src/components/JsonEditor.vue @@ -7,7 +7,7 @@ -
+
@@ -148,9 +148,9 @@ export default { diff --git a/src/components/KeyContentHash.vue b/src/components/KeyContentHash.vue index 1298e43..c0bcd94 100644 --- a/src/components/KeyContentHash.vue +++ b/src/components/KeyContentHash.vue @@ -31,6 +31,7 @@ + float=''> @@ -116,12 +115,7 @@ export default { height: calc(100vh - 286px); } /*json in monaco editor*/ - .key-content-string #monaco-editor-con { - height: calc(100vh - 331px); - } - /*not text viewer box, such as json*/ - .key-content-string .text-formated-container { - box-sizing: border-box; - min-height: calc(100vh - 286px); + .key-content-string .text-formated-container .monaco-editor-con { + height: calc(100vh - 330px); } diff --git a/src/components/KeyContentSet.vue b/src/components/KeyContentSet.vue index cc79a02..2e78bce 100644 --- a/src/components/KeyContentSet.vue +++ b/src/components/KeyContentSet.vue @@ -27,6 +27,7 @@ - Max + Max - Min + Min @@ -39,6 +39,7 @@ + float=''> @@ -117,12 +116,7 @@ export default { height: calc(100vh - 286px); } /*json in monaco editor*/ - .key-content-string #monaco-editor-con { - height: calc(100vh - 331px); - } - /*not text viewer box, such as json*/ - .key-content-string .text-formated-container { - box-sizing: border-box; - min-height: calc(100vh - 286px); + .key-content-string .text-formated-container .monaco-editor-con { + height: calc(100vh - 330px); } diff --git a/src/components/KeyContentZset.vue b/src/components/KeyContentZset.vue index 3fbe748..602ace4 100644 --- a/src/components/KeyContentZset.vue +++ b/src/components/KeyContentZset.vue @@ -30,6 +30,7 @@ + +
+ + + {{ $t('message.load_more_keys') }} + - - - {{ $t('message.load_more_keys') }} - + + + + + {{ $t('message.load_all_keys') }} + + +
@@ -27,8 +45,6 @@ export default { data() { return { keyList: [], - // keyListType: this.config.separator === '' ? 'KeyListNormal' : 'KeyListTree', - // keysPageSize: this.keyListType === 'KeyListNormal' ? 200 : 1000, keyListType: 'KeyListTree', searchPageSize: 10000, scanStreams: [], @@ -37,6 +53,7 @@ export default { onePageList: [], onePageFinishedCount: 0, firstPageFinished: false, + loadAllTooltip: true, }; }, props: ['client', 'config', 'globalSettings'], @@ -46,6 +63,12 @@ export default { let keysPageSize = parseInt(this.globalSettings['keysPageSize']); return keysPageSize ? keysPageSize : 500; }, + showLoadAllKeys(){ + return this.globalSettings['showLoadAllKeys']; + }, + searching() { + return this.$parent.$parent.$parent.$refs.operateItem.searchIcon == 'el-icon-loading'; + }, }, created() { // add or remove key from key list directly @@ -99,15 +122,20 @@ export default { } } }, - initScanStreamsAndScan() { - // this.client.nodes: cluster + loadAllKeys(){ + this.resetKeyList(); + this.$parent.$parent.$parent.$refs.operateItem.searchIcon = 'el-icon-loading'; + this.initScanStreamsAndScan(true); + }, + initScanStreamsAndScan(loadAll = false) { let nodes = this.client.nodes ? this.client.nodes('master') : [this.client]; + let keysPageSize = loadAll ? 50000 : this.keysPageSize; this.scanningCount = nodes.length; nodes.map(node => { let scanOption = { match: this.getMatchMode(), - count: this.keysPageSize, + count: keysPageSize, } // scan count is bigger when in search mode @@ -120,7 +148,7 @@ export default { this.onePageList = this.onePageList.concat(keys); // scan once reaches page size - if (this.onePageList.length >= this.keysPageSize) { + if (this.onePageList.length >= keysPageSize && loadAll === false) { // temp stop stream.pause(); // search input icon recover @@ -129,13 +157,14 @@ export default { // last node refresh keylist if (++this.onePageFinishedCount >= this.scanningCount) { // clear key list only after data scaned, to prevent list jitter + // empty keyList only when first click, if click 'load more' again, do not empty it if (!this.firstPageFinished) { this.firstPageFinished = true; this.keyList = []; } // this page key list append to raw key list - this.keyList = this.keyList.concat(this.onePageList.sort()); + this.keyList = this.keyList.concat(this.onePageList); } } }); @@ -171,14 +200,14 @@ export default { // all nodes scan finished(cusor back to 0) if (--this.scanningCount <= 0) { // clear key list only after data scaned, to prevent list jitter + // empty keyList only when first click, if click 'load more' again, do not empty it if (!this.firstPageFinished) { this.firstPageFinished = true; this.keyList = []; } // this page key list append to raw key list - this.keyList = this.keyList.concat(this.onePageList.sort()); - + this.keyList = this.keyList.concat(this.onePageList); this.scanMoreDisabled = true; // search input icon recover this.$parent.$parent.$parent.$refs.operateItem.searchIcon = 'el-icon-search'; @@ -187,6 +216,9 @@ export default { }); }, resetKeyList(clearKeys = false) { + // cancel scanning + this.cancelScanning(); + clearKeys && (this.keyList = []); this.firstPageFinished = false; this.scanStreams = []; @@ -205,6 +237,13 @@ export default { this.scanMoreDisabled = true; }, + cancelScanning() { + if (this.scanStreams.length) { + for (let stream of this.scanStreams) { + stream.pause && stream.pause(); + } + } + }, getMatchMode(fillStar = true) { let match = this.$parent.$parent.$parent.$refs.operateItem.searchMatch; @@ -262,12 +301,16 @@ export default { diff --git a/src/components/KeyListTree.vue b/src/components/KeyListTree.vue index 116dc2b..188a52b 100644 --- a/src/components/KeyListTree.vue +++ b/src/components/KeyListTree.vue @@ -38,7 +38,8 @@ import $ from "jquery"; if (!window.jQuery) window.jQuery = $; -import ("@ztree/ztree_v3/js/jquery.ztree.all.min.js"); +// import ("@ztree/ztree_v3/js/jquery.ztree.all.js"); +import ("@qii404/ztree_v3/js/jquery.ztree.all.js"); export default { data() { @@ -47,13 +48,14 @@ export default { openStatus: {}, rightClickNode: {}, multiOperating: false, + treeNodesOverflow: 20000, setting: { view: { showIcon: true, showLine: false, selectedMulti: false, dblClickExpand: false, - addDiyDom: this.addDiyDom, + // addDiyDom: this.addDiyDom, expandSpeed: 'fast', }, check: { @@ -69,6 +71,9 @@ export default { // folder clicked if (treeNode.children) { + // after folder expand, sorting keys + !treeNode.open && this.folderExpand(treeNode); + // toggle tree view this.ztreeObj && this.ztreeObj.expandNode(treeNode, undefined, false, false); @@ -78,8 +83,12 @@ export default { this.clickKey(Buffer.from(treeNode.nameBuffer.data), event); }, - onExpand: (event, treeId, treeNode) => { - return this.openStatus[treeNode.fullName] = treeNode.open; + beforeExpand: (treeId, treeNode) => { + // after folder expand, sorting keys + this.folderExpand(treeNode); + this.openStatus[treeNode.fullName] = !treeNode.open; + + return true; }, onCollapse: (event, treeId, treeNode) => { return this.openStatus[treeNode.fullName] = treeNode.open; @@ -187,6 +196,25 @@ export default { toggleCheckAll(checked) { this.ztreeObj.checkAllNodes(checked); }, + folderExpand(treeNode) { + if (!treeNode.asyncOperated) { + // force cut nodes + if (treeNode.children.length > this.treeNodesOverflow) { + treeNode.children.splice(this.treeNodesOverflow); + this.$nextTick(() => { + this.$message.warning({ + message: this.$t('message.tree_node_overflow', {num: this.treeNodesOverflow}), + duration: 4000 + }); + }); + } + + // sort nodes async, only after opened + this.$util.sortKeysAndFolder(treeNode.children); + + treeNode.asyncOperated = true; + } + }, deleteBatch() { let rule = {key: [], pattern: []}; let folderNodes = {}; @@ -277,8 +305,6 @@ export default { }, treeRefresh(nodes) { this.ztreeObj && this.ztreeObj.destroy(); - // folder keep in front - this.$util.sortNodes(nodes); this.ztreeObj = $.fn.zTree.init( $(`#${this.treeId}`), @@ -311,11 +337,26 @@ export default { let checkedNodes = []; this.ztreeObj && (checkedNodes = this.ztreeObj.getCheckedNodes(true)); - this.treeRefresh( - this.separator ? - this.$util.keysToTree(newList, this.separator, this.openStatus) : - this.$util.keysToList(newList) - ); + const keyNodes = this.separator ? + this.$util.keysToTree(newList, this.separator, this.openStatus, this.treeNodesOverflow) : + this.$util.keysToList(newList); + + // nodes displayed in outermost layer + if (keyNodes.length > this.treeNodesOverflow) { + // force cut + keyNodes.splice(this.treeNodesOverflow); + // using nextTick to relieve msg missing caused by app stuck + this.$nextTick(() => { + this.$message.warning({ + message: this.$t('message.tree_node_overflow', {num: this.treeNodesOverflow}), + duration: 6000, + }); + }); + } + + // sort outermost layer nodes + this.$util.sortKeysAndFolder(keyNodes); + this.treeRefresh(keyNodes); // remove animination when too many nodes if (newList.length > 9000) { @@ -324,7 +365,6 @@ export default { // recheck checked nodes this.reCheckNodes(checkedNodes); - // only 1 key such as extract search, expand all if (newList.length <= 1) { this.ztreeObj && this.ztreeObj.expandAll(true); @@ -335,7 +375,7 @@ export default {