mirror of
https://github.com/qishibo/AnotherRedisDesktopManager.git
synced 2026-01-18 16:12:43 +00:00
Feature/load all keys (#770)
Load all keys support! due to conflict, can only merge via "squash and merge" Co-authored-by: Ricardo Tondello <rkdtondello@gmail.com>
This commit is contained in:
parent
0778323150
commit
261a7de2cf
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ export default {
|
||||
nodes.map(node => {
|
||||
let scanOption = {
|
||||
match: pattern + '*',
|
||||
count: 7000,
|
||||
count: 50000,
|
||||
}
|
||||
|
||||
let stream = node.scanBufferStream(scanOption);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
|
||||
<!-- monaco editor div -->
|
||||
<div id="monaco-editor-con" ref="editor"></div>
|
||||
<div class="monaco-editor-con" ref="editor"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -148,9 +148,9 @@ export default {
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.text-formated-container #monaco-editor-con {
|
||||
.text-formated-container .monaco-editor-con {
|
||||
min-height: 150px;
|
||||
max-height: 100vh;
|
||||
height: calc(100vh - 730px);
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
@ -175,14 +175,14 @@ export default {
|
||||
background: #565656;
|
||||
}
|
||||
|
||||
.text-formated-container #monaco-editor-con .monaco-editor .slider {
|
||||
.text-formated-container .monaco-editor-con .monaco-editor .slider {
|
||||
border-radius: 4px;
|
||||
background: #c1c1c1;
|
||||
}
|
||||
.dark-mode .text-formated-container #monaco-editor-con .monaco-editor .slider {
|
||||
.dark-mode .text-formated-container .monaco-editor-con .monaco-editor .slider {
|
||||
background: #5d676d;
|
||||
}
|
||||
.text-formated-container #monaco-editor-con .monaco-editor .slider:hover {
|
||||
.text-formated-container .monaco-editor-con .monaco-editor .slider:hover {
|
||||
background: #7d7d7d;
|
||||
}
|
||||
|
||||
@ -190,39 +190,39 @@ export default {
|
||||
.text-formated-container .monaco-editor .margin {
|
||||
background-color: inherit;
|
||||
}
|
||||
#monaco-editor-con .monaco-editor, #monaco-editor-con .monaco-editor-background, #monaco-editor-con .monaco-editor .inputarea.ime-input {
|
||||
.monaco-editor-con .monaco-editor, .monaco-editor-con .monaco-editor-background, .monaco-editor-con .monaco-editor .inputarea.ime-input {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
/*json key color*/
|
||||
#monaco-editor-con .mtk4 {
|
||||
.monaco-editor-con .mtk4 {
|
||||
color: #111111;
|
||||
}
|
||||
.dark-mode #monaco-editor-con .mtk4 {
|
||||
.dark-mode .monaco-editor-con .mtk4 {
|
||||
color: #ebebec;
|
||||
}
|
||||
/*json val string color*/
|
||||
#monaco-editor-con .mtk5 {
|
||||
.monaco-editor-con .mtk5 {
|
||||
color: #42b983;
|
||||
}
|
||||
/*json val number color*/
|
||||
#monaco-editor-con .mtk6 {
|
||||
.monaco-editor-con .mtk6 {
|
||||
color: #fc1e70;
|
||||
}
|
||||
/*json bracket color*/
|
||||
#monaco-editor-con .mtk9 {
|
||||
.monaco-editor-con .mtk9 {
|
||||
color: #111111;
|
||||
}
|
||||
/*json bracket color*/
|
||||
.dark-mode #monaco-editor-con .mtk9 {
|
||||
.dark-mode .monaco-editor-con .mtk9 {
|
||||
color: #b6b6b9;
|
||||
}
|
||||
|
||||
/* common string in json editor*/
|
||||
#monaco-editor-con .mtk1 {
|
||||
.monaco-editor-con .mtk1 {
|
||||
color: #606266;
|
||||
}
|
||||
.dark-mode #monaco-editor-con .mtk1 {
|
||||
.dark-mode .monaco-editor-con .mtk1 {
|
||||
color: #f3f3f4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
<el-table
|
||||
stripe
|
||||
border
|
||||
size='mini'
|
||||
min-height=300
|
||||
:data="hashData">
|
||||
<el-table-column
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
<el-table
|
||||
stripe
|
||||
border
|
||||
size='mini'
|
||||
min-height=300
|
||||
:data="listData">
|
||||
<el-table-column
|
||||
|
||||
@ -7,8 +7,7 @@
|
||||
:content='content'
|
||||
:binary='binary'
|
||||
:redisKey='redisKey'
|
||||
float=''
|
||||
:textrows=12>
|
||||
float=''>
|
||||
</FormatViewer>
|
||||
</el-form-item>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
<el-table
|
||||
stripe
|
||||
border
|
||||
size='mini'
|
||||
min-height=300
|
||||
:data="setData">
|
||||
<el-table-column
|
||||
|
||||
@ -8,11 +8,11 @@
|
||||
</el-form-item>
|
||||
<!-- max value -->
|
||||
<el-form-item label="Max">
|
||||
<el-input v-model="maxId" @keyup.enter.native='initShow' type="primary" placeholder='Max ID, default +' :title='$t("message.enter_to_search")'>Max</el-input>
|
||||
<el-input v-model="maxId" @keyup.enter.native='initShow' type="primary" placeholder='Max ID, default +' :title='$t("message.enter_to_search")' size='mini'>Max</el-input>
|
||||
</el-form-item>
|
||||
<!-- min value -->
|
||||
<el-form-item label="Min">
|
||||
<el-input v-model="minId" @keyup.enter.native='initShow' type="primary" placeholder='Min ID, default -' :title='$t("message.enter_to_search")'>Min</el-input>
|
||||
<el-input v-model="minId" @keyup.enter.native='initShow' type="primary" placeholder='Min ID, default -' :title='$t("message.enter_to_search")' size='mini'>Min</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@ -39,6 +39,7 @@
|
||||
<el-table
|
||||
stripe
|
||||
border
|
||||
size='mini'
|
||||
min-height=300
|
||||
:data="lineData">
|
||||
<el-table-column
|
||||
|
||||
@ -7,8 +7,7 @@
|
||||
:content='content'
|
||||
:binary='binary'
|
||||
:redisKey='redisKey'
|
||||
float=''
|
||||
:textrows=12>
|
||||
float=''>
|
||||
</FormatViewer>
|
||||
</el-form-item>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
<el-table
|
||||
stripe
|
||||
border
|
||||
size='mini'
|
||||
min-height=300
|
||||
:data="zsetData">
|
||||
<el-table-column
|
||||
|
||||
@ -7,15 +7,33 @@
|
||||
:client="client"
|
||||
:keyList="keyList">
|
||||
</component>
|
||||
|
||||
<div class='keys-load-more-wrapper'>
|
||||
<!-- load more -->
|
||||
<el-button
|
||||
ref='scanMoreBtn'
|
||||
class='load-more-keys'
|
||||
:disabled='scanMoreDisabled || searching'
|
||||
@click='refreshKeyList(false)'>
|
||||
{{ $t('message.load_more_keys') }}
|
||||
</el-button>
|
||||
|
||||
<!-- load more -->
|
||||
<el-button
|
||||
ref='scanMoreBtn'
|
||||
class='load-more-keys'
|
||||
:disabled='scanMoreDisabled'
|
||||
@click='refreshKeyList(false)'>
|
||||
{{ $t('message.load_more_keys') }}
|
||||
</el-button>
|
||||
<!-- load all -->
|
||||
<!-- fix el-tooltip 200ms delay when closing -->
|
||||
<el-tooltip v-if='showLoadAllKeys' :disabled="!loadAllTooltip"
|
||||
@mouseenter.native="loadAllTooltip=true" @mouseleave.native="loadAllTooltip=false"
|
||||
effect="dark" :content="$t('message.load_all_keys_tip')"
|
||||
placement="bottom" :open-delay=380 :enterable='false'>
|
||||
<el-button
|
||||
class='load-more-keys'
|
||||
type= 'danger'
|
||||
:icon="searching ? 'el-icon-loading' : ''"
|
||||
:disabled='searching'
|
||||
@click='loadAllKeys()'>
|
||||
{{ $t('message.load_all_keys') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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 {
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.load-more-keys {
|
||||
margin: 10px auto;
|
||||
.keys-load-more-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
.keys-load-more-wrapper .load-more-keys {
|
||||
margin: 10px 5px;
|
||||
padding: 0;
|
||||
display: block;
|
||||
height: 20px;
|
||||
height: 22px;
|
||||
width: 100%;
|
||||
font-size: 75%;
|
||||
line-height: 1px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -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 {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '@ztree/ztree_v3/css/zTreeStyle/zTreeStyle.css';
|
||||
@import '@qii404/ztree_v3/css/zTreeStyle/zTreeStyle.css';
|
||||
|
||||
/*tree style*/
|
||||
.ztree {
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<!-- new key dialog -->
|
||||
<el-dialog :title="$t('message.add_new_key')" :visible.sync="newKeyDialog">
|
||||
<el-dialog :title="$t('message.add_new_key')" :visible.sync="newKeyDialog" :close-on-click-modal='false'>
|
||||
<el-form label-position="top" size="mini">
|
||||
<el-form-item :label="$t('message.key_name')">
|
||||
<el-input v-model='newKeyName'></el-input>
|
||||
|
||||
@ -45,26 +45,20 @@
|
||||
timer = requestAnimationFrame(function fn() {
|
||||
const nowTop = that.realDom.scrollTop;
|
||||
|
||||
if (nowTop > 5000) {
|
||||
that.realDom.scrollTop -= 2500;
|
||||
timer = requestAnimationFrame(fn);
|
||||
// to top already
|
||||
if (nowTop <= 0) {
|
||||
cancelAnimationFrame(timer);
|
||||
that.toTopShow = false;
|
||||
}
|
||||
else if (nowTop > 1000 && nowTop <= 5000) {
|
||||
that.realDom.scrollTop -= 500;
|
||||
timer = requestAnimationFrame(fn);
|
||||
}
|
||||
else if (nowTop > 200 && nowTop <= 1000) {
|
||||
that.realDom.scrollTop -= 100;
|
||||
timer = requestAnimationFrame(fn);
|
||||
}
|
||||
else if (nowTop > 0 && nowTop <= 200) {
|
||||
that.realDom.scrollTop -= 15;
|
||||
|
||||
else if (nowTop < 50) {
|
||||
that.realDom.scrollTop -= 5;
|
||||
timer = requestAnimationFrame(fn);
|
||||
}
|
||||
|
||||
else {
|
||||
cancelAnimationFrame(timer);
|
||||
that.toTopShow = false;
|
||||
that.realDom.scrollTop -= nowTop * 0.2;
|
||||
timer = requestAnimationFrame(fn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -36,7 +36,10 @@
|
||||
:max=10000
|
||||
:step=50
|
||||
v-model='form.keysPageSize'>
|
||||
</el-input-number>
|
||||
</el-input-number>
|
||||
<!-- load all switch -->
|
||||
<el-switch v-model='form.showLoadAllKeys'></el-switch> {{ $t('message.show_load_all_keys') }}
|
||||
|
||||
<span slot="label">
|
||||
{{ $t('message.keys_per_loading') }}
|
||||
<el-popover
|
||||
@ -133,7 +136,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
form: {fontFamily: '', zoomFactor: 1.0, keysPageSize: 500},
|
||||
form: {fontFamily: '', zoomFactor: 1.0, keysPageSize: 500, showLoadAllKeys: false},
|
||||
importConnectionVisible: false,
|
||||
connectionFileContent: '',
|
||||
appVersion: (new URL(window.location.href)).searchParams.get('version'),
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- </textarea> -->
|
||||
<el-input ref='textInput' :disabled='disabled' type='textarea' :rows='textrows' :value='contentDisplay'></el-input>
|
||||
<el-input ref='textInput' :disabled='disabled' type='textarea' :value='contentDisplay'></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
props: ['content', 'contentVisible', 'textrows', 'disabled'],
|
||||
props: ['content', 'contentVisible', 'disabled'],
|
||||
computed: {
|
||||
contentDisplay() {
|
||||
return this.$util.bufToBinary(this.content);
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- </textarea> -->
|
||||
<el-input ref='textInput' :disabled='disabled' type='textarea' :rows='textrows' :value='contentDisplay'></el-input>
|
||||
<el-input ref='textInput' :disabled='disabled' type='textarea' :value='contentDisplay'></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
props: ['content', 'contentVisible', 'textrows', 'disabled'],
|
||||
props: ['content', 'contentVisible', 'disabled'],
|
||||
computed: {
|
||||
contentDisplay() {
|
||||
return this.$util.bufToString(this.content);
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
:title='alertTitle'
|
||||
type="error">
|
||||
</el-alert>
|
||||
<el-input :disabled='true' type='textarea' :rows='textrows' :value='contentDisplay'></el-input>
|
||||
<el-input :disabled='true' type='textarea' :value='contentDisplay'></el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -16,7 +16,7 @@ export default {
|
||||
firstChars: 20000,
|
||||
};
|
||||
},
|
||||
props: ['content', 'contentVisible', 'textrows', 'disabled'],
|
||||
props: ['content', 'contentVisible', 'disabled'],
|
||||
computed: {
|
||||
contentDisplay() {
|
||||
return this.$util.bufToString(this.content.slice(0, this.firstChars), false)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- </textarea> -->
|
||||
<el-input ref='textInput' :disabled='disabled' type='textarea' :rows='textrows' :value='contentDisplay' @input='inputContent'>
|
||||
<el-input ref='textInput' :disabled='disabled' type='textarea' :value='contentDisplay' @input='inputContent'>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
@ -13,7 +13,7 @@ export default {
|
||||
confirmChange: false,
|
||||
};
|
||||
},
|
||||
props: ['content', 'contentVisible', 'textrows', 'disabled'],
|
||||
props: ['content', 'contentVisible', 'disabled'],
|
||||
computed: {
|
||||
contentDisplay() {
|
||||
return this.content.toString();
|
||||
|
||||
@ -146,6 +146,10 @@ const cn = {
|
||||
hide_window: '隐藏窗口',
|
||||
minimize_window: '最小化窗口',
|
||||
maximize_window: '最大化窗口',
|
||||
load_all_keys: '加载所有',
|
||||
show_load_all_keys: '启用按钮以加载所有键',
|
||||
load_all_keys_tip: '一次性加载所有key,当key的数量过多时,有可能会导致客户端卡顿,请酌情使用',
|
||||
tree_node_overflow: 'key或者文件夹数量过多,仅保留{num}个进行展示。如未找到所需key,建议使用模糊搜索,或者设置分隔符来将key分散到文件夹中',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ const de = {
|
||||
hide_window: 'Fenster ausblenden',
|
||||
minimize_window: 'Fenster minimieren',
|
||||
maximize_window: 'Fenster maximieren',
|
||||
load_all_keys: 'alle laden',
|
||||
show_load_all_keys: 'Schaltfläche aktivieren, um alle Schlüssel zu laden',
|
||||
load_all_keys_tip: 'Alle Schlüssel auf einmal laden. Wenn die Anzahl der Schlüssel zu groß ist, kann der Client stecken bleiben. Bitte verwenden Sie es richtig',
|
||||
tree_node_overflow: 'Zu viele Schlüssel oder Ordner, behalten Sie nur {num} für die Anzeige. Wenn Ihr Schlüssel nicht hier ist, wird eine unscharfe Suche empfohlen, oder den Trenner setzen, um die Schlüssel in Ordner zu verteilen',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ const en = {
|
||||
hide_window: 'Hide Window',
|
||||
minimize_window: 'Minimize window',
|
||||
maximize_window: 'Maximize window',
|
||||
load_all_keys: 'load all',
|
||||
show_load_all_keys: 'Enable button to load all keys',
|
||||
load_all_keys_tip: 'Load all keys at one time. If the number of keys is too large, the client may get stuck. Please use it correctly',
|
||||
tree_node_overflow: 'Too many keys or folders , keep only {num} for display. If your key is not here, fuzzy search is recommended, or set the separator to spread the keys into folders',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ const fr = {
|
||||
hide_window: 'Masquer la fenêtre',
|
||||
minimize_window: 'Réduire la fenêtre',
|
||||
maximize_window: 'Agrandir la fenêtre',
|
||||
load_all_keys: 'charger tout',
|
||||
show_load_all_keys: 'Activer le bouton pour charger toutes les clés',
|
||||
load_all_keys_tip: 'Chargez toutes les clés en même temps. Si le nombre de clés est trop important, le client peut rester bloqué. Veuillez l\'utiliser correctement',
|
||||
tree_node_overflow: 'Il y a trop de touches ou de dossiers, ne laissant que {num} à afficher. Si votre clé n\'est pas ici, une recherche floue est recommandée, ou définissez un séparateur pour disperser la clé dans le dossier',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ const it = {
|
||||
private_key_faq: 'La chiave privata in formato RSA è compatibile e inizia con <pre>-----BEGIN RSA PRIVATE KEY-----</pre>\
|
||||
ma se inizi con<pre>-----BEGIN OPENSSH PRIVATE KEY-----</pre>è necessario convertire il formato tramite <pre>ssh-keygen -p -m pem -f ~/.ssh/id_rsa</pre>Questa operazione non influirà sul precedente accesso con chiave privata',
|
||||
dark_mode: 'Modalità scura',
|
||||
load_more_keys: 'Carica più chiavi',
|
||||
load_more_keys: 'carica di più',
|
||||
key_name: 'Nome della chiave',
|
||||
project_home: 'Progetto Home',
|
||||
cluster_faq: 'Seleziona qualsiasi nodo nel cluster da compilare e gli altri nodi verranno identificati automaticamente.',
|
||||
@ -146,6 +146,10 @@ const it = {
|
||||
hide_window: 'Nascondi finestra',
|
||||
minimize_window: 'Riduci finestra',
|
||||
maximize_window: 'Massimizza finestra',
|
||||
load_all_keys: 'carica tutto',
|
||||
show_load_all_keys: 'Abilita pulsante per caricare tutte le chiavi',
|
||||
load_all_keys_tip: 'Carica tutte le chiavi contemporaneamente. Se il numero di chiavi è troppo grande, il client potrebbe rimanere bloccato. Si prega di usarlo correttamente',
|
||||
tree_node_overflow: 'Troppi tasti o cartelle, tenere solo {num} per la visualizzazione. Se la tua chiave non è qui, si raccomanda la ricerca sfocata, o impostare il separatore per distribuire le chiavi in cartelle',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -94,7 +94,7 @@ const pt = {
|
||||
private_key_faq: 'A chave privada de formato RSA é compatível e começa com <pre>-----BEGIN RSA PRIVATE KEY-----</pre>\
|
||||
mas se começar com<pre>-----BEGIN OPENSSH PRIVATE KEY-----</pre>você precisa converter o formato via <pre>ssh-keygen -p -m pem -f ~/.ssh/id_rsa</pre>Esta operação não afetará o login de chave privada anterior',
|
||||
dark_mode: 'Modo noturno',
|
||||
load_more_keys: 'Carregar mais chaves',
|
||||
load_more_keys: 'carregar mais',
|
||||
key_name: 'Nome da Chave',
|
||||
project_home: 'Home do Projeto',
|
||||
cluster_faq: 'Selecione qualquer nó no cluster para preencher e outros nós serão identificados automaticamente.',
|
||||
@ -146,6 +146,10 @@ const pt = {
|
||||
hide_window: 'Ocultar janela',
|
||||
minimize_window: 'Minimize a janela',
|
||||
maximize_window: 'Maximize a janela',
|
||||
load_all_keys: 'carregar tudo',
|
||||
show_load_all_keys: 'Habilite o botão para carregar todas as chaves',
|
||||
load_all_keys_tip: 'Carregue todas as chaves de uma vez. Se o número de chaves for muito grande, o cliente pode ficar preso. Por favor, use-o corretamente',
|
||||
tree_node_overflow: 'Muitas teclas ou pastas, manter apenas {num} para exibição. Se a sua chave não está aqui, é recomendada a pesquisa difusa, ou configurar o separador para espalhar as chaves em pastas',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ const ru = {
|
||||
hide_window: 'Скрыть окно',
|
||||
minimize_window: 'Свернуть окно',
|
||||
maximize_window: 'Развернуть окно',
|
||||
load_all_keys: 'загрузить все',
|
||||
show_load_all_keys: 'Кнопка включения для загрузки всех ключей',
|
||||
load_all_keys_tip: 'Загрузите все ключи одновременно. Если количество ключей слишком велико, клиент может застрять. Пожалуйста, используйте его правильно',
|
||||
tree_node_overflow: 'Слишком много клавиш или папок, оставьте только {num} для отображения. Если вашего ключа здесь нет, рекомендуется использовать нечеткий поиск, или Настройка разделителя для разделения ключей в папку',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ const tr = {
|
||||
hide_window: 'Pencereyi gizle',
|
||||
minimize_window: 'Pencereleri küçültür',
|
||||
maximize_window: 'Pencereyi büyüt',
|
||||
load_all_keys: 'hepsini yükle',
|
||||
show_load_all_keys: 'Tüm anahtarları yüklemek için etkinleştir düğmesi',
|
||||
load_all_keys_tip: 'Tüm anahtarları bir kerede yükleyin. Anahtar sayısı çok fazlaysa, istemci takılabilir. Lütfen doğru kullanın',
|
||||
tree_node_overflow: 'Çok fazla anahtar veya klasör var, yalnızca {num} görüntüleme için ayrıldı. Anahtarınız burada değilse bulanık arama önerilir, veya anahtarı klasöre yaymak için bir ayırıcı ayarlayın',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ const tw = {
|
||||
hide_window: '隱藏窗口',
|
||||
minimize_window: '最小化窗口',
|
||||
maximize_window: '最大化窗口',
|
||||
load_all_keys: '載入所有',
|
||||
show_load_all_keys: '啟用按鈕以加載所有鍵',
|
||||
load_all_keys_tip: '一次載入所有密鑰。如果密鑰數量過多,客戶端可能會卡住,請正確使用',
|
||||
tree_node_overflow: '鍵或資料夾太多,僅保留{num}個以顯示。 如果您的鍵不在此處,建議使用模糊搜索,或設置分隔符以將密鑰分散到文件夾中',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ const ua = {
|
||||
hide_window: 'Сховати вікно',
|
||||
minimize_window: 'Згорнути вікно',
|
||||
maximize_window: 'Розгорнути вікно',
|
||||
load_all_keys: 'завантажити все',
|
||||
show_load_all_keys: 'Кнопка «Увімкнути», щоб завантажити всі ключі',
|
||||
load_all_keys_tip: 'Завантажте всі ключі одночасно. Якщо кількість ключів занадто велика, клієнт може застрягти. Будь ласка, використовуйте його правильно',
|
||||
tree_node_overflow: 'Занадто багато ключів або папок, тільки {num} дисплея. Якщо ваш ключ не тут, рекомендується використовувати нечіткий пошук, або встановити роздільник для поширення ключів у теки',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
41
src/util.js
41
src/util.js
@ -172,7 +172,7 @@ export default {
|
||||
};
|
||||
});
|
||||
},
|
||||
keysToTree(keys, separator = ':', openStatus = {}) {
|
||||
keysToTree(keys, separator = ':', openStatus = {}, forceCut = 20000) {
|
||||
let tree = {};
|
||||
keys.forEach(key => {
|
||||
let currentNode = tree;
|
||||
@ -197,9 +197,10 @@ export default {
|
||||
});
|
||||
});
|
||||
|
||||
return this.formatTreeData(tree, '', openStatus, separator)
|
||||
// to tree format
|
||||
return this.formatTreeData(tree, '', openStatus, separator, forceCut);
|
||||
},
|
||||
formatTreeData(tree, previousKey = '', openStatus = {}, separator = ':') {
|
||||
formatTreeData(tree, previousKey = '', openStatus = {}, separator = ':', forceCut = 20000) {
|
||||
return Object.keys(tree).map(key => {
|
||||
let node = { name: key ? key : '[Empty]'};
|
||||
|
||||
@ -207,10 +208,13 @@ export default {
|
||||
if (!tree[key].keyNode && Object.keys(tree[key]).length > 0) {
|
||||
let tillNowKeyName = previousKey + key + separator;
|
||||
node.open = !!openStatus[tillNowKeyName];
|
||||
node.children = this.formatTreeData(tree[key], tillNowKeyName, openStatus, separator);
|
||||
// keep folder node in top of the tree(not include the outest list)
|
||||
this.sortNodes(node.children);
|
||||
node.children = this.formatTreeData(tree[key], tillNowKeyName, openStatus, separator, forceCut);
|
||||
node.keyCount = node.children.reduce((a, b) => a + (b.keyCount || 0), 0);
|
||||
// too many children, force cut, do not incluence keyCount display
|
||||
node.open && node.children.length > forceCut && node.children.splice(forceCut);
|
||||
// keep folder node in front of the tree and sorted(not include the outest list)
|
||||
// async sort, only for opened folders
|
||||
node.open && this.sortKeysAndFolder(node.children);
|
||||
node.fullName = tillNowKeyName;
|
||||
}
|
||||
// key node
|
||||
@ -223,14 +227,27 @@ export default {
|
||||
return node;
|
||||
});
|
||||
},
|
||||
// nodes is reference
|
||||
sortNodes(nodes) {
|
||||
// nodes is reference, keep folder in front and sorted,
|
||||
// keep keys in tail and sorted
|
||||
sortKeysAndFolder(nodes) {
|
||||
nodes.sort(function(a, b) {
|
||||
if (a.children && b.children) {
|
||||
return 0;
|
||||
// a & b are all keys
|
||||
if (!a.children && !b.children) {
|
||||
return a.name > b.name ? 1 : -1;
|
||||
}
|
||||
// a & b are all folder
|
||||
else if (a.children && b.children) {
|
||||
return a.name > b.name ? 1 : -1;
|
||||
}
|
||||
|
||||
// a is folder, b is key
|
||||
else if (a.children) {
|
||||
return -1;
|
||||
}
|
||||
// a is key, b is folder
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return a.children ? -1 : (b.children ? 1 : 0);
|
||||
});
|
||||
},
|
||||
copyToClipboard(text) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user