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 {