feat: add key and prefix reload

refactor: separate update db tree node logic from open database
This commit is contained in:
tiny-craft 2023-07-04 22:56:57 +08:00
parent e906a91964
commit a200b554de
14 changed files with 209 additions and 998 deletions

View File

@ -307,18 +307,27 @@ func (c *connectionService) parseDBItemInfo(info string) map[string]int {
// OpenDatabase open select database, and list all keys
// @param path contain connection name and db name
func (c *connectionService) OpenDatabase(connName string, db int) (resp types.JSResp) {
return c.ScanKeys(connName, db, "*")
}
// ScanKeys scan all keys below prefix
func (c *connectionService) ScanKeys(connName string, db int, prefix string) (resp types.JSResp) {
rdb, ctx, err := c.getRedisClient(connName, db)
if err != nil {
resp.Msg = err.Error()
return
}
if !strings.HasSuffix(prefix, "*") {
prefix += ":*"
}
//var keys []string
keys := map[string]keyItem{}
var cursor uint64
for {
var loadedKey []string
loadedKey, cursor, err = rdb.Scan(ctx, cursor, "*", 10000).Result()
loadedKey, cursor, err = rdb.Scan(ctx, cursor, prefix, 10000).Result()
if err != nil {
resp.Msg = err.Error()
return

View File

@ -10,9 +10,9 @@
"dependencies": {
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"pinia": "^2.1.3",
"sass": "^1.62.1",
"vue": "^3.2.37",
"pinia": "^2.1.4",
"sass": "^1.63.6",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2"
},
"devDependencies": {
@ -20,9 +20,9 @@
"naive-ui": "^2.34.4",
"prettier": "^2.8.8",
"unplugin-auto-import": "^0.16.4",
"unplugin-icons": "^0.16.1",
"unplugin-vue-components": "^0.25.0",
"vite": "^4.3.0"
"unplugin-icons": "^0.16.3",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.3.9"
}
},
"node_modules/@antfu/install-pkg": {
@ -1503,9 +1503,9 @@
}
},
"node_modules/pinia": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.3.tgz",
"integrity": "sha512-XNA/z/ye4P5rU1pieVmh0g/hSuDO98/a5UC8oSP0DNdvt6YtetJNHTrXwpwsQuflkGT34qKxAEcp7lSxXNjf/A==",
"version": "2.1.4",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.4.tgz",
"integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
@ -1655,9 +1655,9 @@
}
},
"node_modules/sass": {
"version": "1.63.2",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.63.2.tgz",
"integrity": "sha512-u56TU0AIFqMtauKl/OJ1AeFsXqRHkgO7nCWmHaDwfxDo9GUMSqBA4NEh6GMuh1CYVM7zuROYtZrHzPc2ixK+ww==",
"version": "1.63.6",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.63.6.tgz",
"integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -3113,9 +3113,9 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"pinia": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.3.tgz",
"integrity": "sha512-XNA/z/ye4P5rU1pieVmh0g/hSuDO98/a5UC8oSP0DNdvt6YtetJNHTrXwpwsQuflkGT34qKxAEcp7lSxXNjf/A==",
"version": "2.1.4",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.4.tgz",
"integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==",
"requires": {
"@vue/devtools-api": "^6.5.0",
"vue-demi": ">=0.14.5"
@ -3212,9 +3212,9 @@
}
},
"sass": {
"version": "1.63.2",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.63.2.tgz",
"integrity": "sha512-u56TU0AIFqMtauKl/OJ1AeFsXqRHkgO7nCWmHaDwfxDo9GUMSqBA4NEh6GMuh1CYVM7zuROYtZrHzPc2ixK+ww==",
"version": "1.63.6",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.63.6.tgz",
"integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==",
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",

View File

@ -11,9 +11,9 @@
"dependencies": {
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"pinia": "^2.1.3",
"sass": "^1.62.1",
"vue": "^3.2.37",
"pinia": "^2.1.4",
"sass": "^1.63.6",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2"
},
"devDependencies": {
@ -21,8 +21,8 @@
"naive-ui": "^2.34.4",
"prettier": "^2.8.8",
"unplugin-auto-import": "^0.16.4",
"unplugin-icons": "^0.16.1",
"unplugin-vue-components": "^0.25.0",
"vite": "^4.3.0"
"unplugin-icons": "^0.16.3",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.3.9"
}
}

View File

@ -1 +1 @@
da66eb9d13a7ace25f7f75d36c2510f9
e8efe46ff15777b1af82225bac5f4626

View File

@ -56,7 +56,7 @@ const onDeleteKey = () => {
<redis-type-tag :type="props.keyType" size="large"></redis-type-tag>
<n-input v-model:value="props.keyPath">
<template #suffix>
<icon-button :icon="Refresh" t-tooltip="reload_key" size="18" @click="onReloadKey" />
<icon-button :icon="Refresh" t-tooltip="reload" size="18" @click="onReloadKey" />
</template>
</n-input>
</n-input-group>

View File

@ -152,7 +152,7 @@ const onClose = () => {
preset="dialog"
transform-origin="center"
>
<n-tabs v-model:value="tab">
<n-tabs v-model:value="tab" type="line">
<n-tab-pane :tab="$t('general')" display-directive="show" name="general">
<n-form
ref="generalFormRef"

View File

@ -116,7 +116,7 @@ const onClose = () => {
preset="dialog"
transform-origin="center"
>
<n-tabs v-model:value="tab">
<n-tabs v-model:value="tab" type="line">
<n-tab-pane :tab="$t('general')" display-directive="show" name="general">
<n-form
:label-width="formLabelWidth"

View File

@ -1,10 +1,10 @@
<script setup>
import { h, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { h, nextTick, onMounted, reactive, ref } from 'vue'
import { ConnectionType } from '../../consts/connection_type.js'
import { NIcon, useDialog, useMessage } from 'naive-ui'
import Key from '../icons/Key.vue'
import ToggleDb from '../icons/ToggleDb.vue'
import { get, indexOf, size } from 'lodash'
import { indexOf, isEmpty } from 'lodash'
import { useI18n } from 'vue-i18n'
import Refresh from '../icons/Refresh.vue'
import CopyLink from '../icons/CopyLink.vue'
@ -158,7 +158,20 @@ const onUpdateExpanded = (value, option, meta) => {
}
}
const onUpdateSelectedKeys = (keys, option) => {
const onUpdateSelectedKeys = (keys, options) => {
if (!isEmpty(options)) {
// prevent load duplicate key
for (const node of options) {
if (node.type === ConnectionType.RedisValue) {
const { key, name, db, redisKey } = node
if (indexOf(selectedKeys.value, key) === -1) {
connectionStore.loadKeyValue(name, db, redisKey)
}
break
}
}
}
selectedKeys.value = keys
}
@ -210,13 +223,6 @@ const renderSuffix = ({ option }) => {
const nodeProps = ({ option }) => {
return {
onClick() {
const { key, name, db, type, redisKey } = option
if (option.type === ConnectionType.RedisValue) {
connectionStore.loadKeyValue(name, db, redisKey)
}
// console.log('[click]:' + JSON.stringify(option))
},
onDblclick: async () => {
if (loading.value) {
console.warn('TODO: alert to ignore double click when loading')
@ -279,6 +285,12 @@ const handleSelectContextMenu = (key) => {
case 'key_newkey':
dialogStore.openNewKeyDialog(redisKey, name, db)
break
case 'key_reload':
connectionStore.scanKeys(name, db, redisKey)
break
case 'value_reload':
connectionStore.loadKeyValue(name, db, redisKey)
break
case 'key_remove':
case 'value_remove':
confirmDialog.warning(i18n.t('remove_tip', { name: redisKey }), () => {

View File

@ -11,7 +11,6 @@
"disconnect_all": "Disconnect all connections",
"filter": "Filter",
"sort_conn": "Resort Connections",
"reload_key": "Reload Current Key",
"close_confirm": "Confirm close this tab and connection",
"opening_connection": "Opening Connection...",
"remove_tip": "{type} \"{name}\" will be deleted",

View File

@ -11,7 +11,6 @@
"disconnect_all": "断开所有连接",
"filter": "筛选",
"sort_conn": "调整连接顺序",
"reload_key": "重新载入此键内容",
"close_confirm": "是否关闭当前连接",
"opening_connection": "正在打开连接...",
"remove_tip": "{type} \"{name}\" 将会被删除",

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { get, isEmpty, last, map, remove, size, sortedIndexBy, split, uniq } from 'lodash'
import { findIndex, get, isEmpty, last, map, remove, size, sortedIndexBy, split, uniq } from 'lodash'
import {
AddHashField,
AddListItem,
@ -18,6 +18,7 @@ import {
RenameKey,
SaveConnection,
SaveSortedConnection,
ScanKeys,
SetHashValue,
SetKeyTTL,
SetKeyValue,
@ -389,78 +390,8 @@ const useConnectionStore = defineStore('connections', {
return
}
// insert child to children list by order
const sortedInsertChild = (childrenList, item) => {
const insertIdx = sortedIndexBy(childrenList, item, 'key')
childrenList.splice(insertIdx, 0, item)
// childrenList.push(item)
}
// update all node item's children num
const updateChildrenNum = (node) => {
let count = 0
const totalChildren = size(node.children)
if (totalChildren > 0) {
for (const elem of node.children) {
updateChildrenNum(elem)
count += elem.keys
}
} else {
count += 1
}
node.keys = count
// node.children = sortBy(node.children, 'label')
}
const keyStruct = []
const mark = {}
for (const key in keys) {
const keyPart = split(key, separator)
// const prefixLen = size(keyPart) - 1
const len = size(keyPart)
let handlePath = ''
let ks = keyStruct
for (let i = 0; i < len; i++) {
handlePath += keyPart[i]
if (i !== len - 1) {
// layer
const treeKey = `${handlePath}@${ConnectionType.RedisKey}`
if (!mark.hasOwnProperty(treeKey)) {
mark[treeKey] = {
key: `${connName}/db${db}/${treeKey}`,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
type: ConnectionType.RedisKey,
children: [],
}
sortedInsertChild(ks, mark[treeKey])
}
ks = mark[treeKey].children
handlePath += separator
} else {
// key
const treeKey = `${handlePath}@${ConnectionType.RedisValue}`
mark[treeKey] = {
key: `${connName}/db${db}/${treeKey}`,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
type: ConnectionType.RedisValue,
}
sortedInsertChild(ks, mark[treeKey])
}
}
}
// append db node to current connection's children
const dbs = this.databases[connName]
dbs[db].children = keyStruct
dbs[db].opened = true
updateChildrenNum(dbs[db])
this._updateNodeChildren(connName, db, keys)
},
/**
@ -491,7 +422,127 @@ const useConnectionStore = defineStore('connections', {
},
/**
*
* scan keys with prefix
* @param {string} connName
* @param {number} db
* @param {string} prefix
* @returns {Promise<void>}
*/
async scanKeys(connName, db, prefix) {
const { data, success, msg } = await ScanKeys(connName, db, prefix)
if (!success) {
throw new Error(msg)
}
// remove current keys below prefix
const prefixPart = split(prefix, separator)
const dbs = this.databases[connName]
let node = dbs[db]
for (const key of prefixPart) {
const idx = findIndex(node.children, { label: key })
if (idx === -1) {
node = null
break
}
node = node.children[idx]
}
if (node != null) {
node.children = []
}
const { keys = [] } = data
this._updateNodeChildren(connName, db, keys)
},
/**
* remove keys in db
* @param {string} connName
* @param {number} db
* @param {Object.<string, {}>[]} keys
* @private
*/
_updateNodeChildren(connName, db, keys) {
// find match key node in node list
const findNodeByKey = (nodes, key) => {
const idx = findIndex(nodes, { key })
return idx !== -1 ? nodes[idx] : null
}
// insert child to children list by order
const sortedInsertChild = (childrenList, item) => {
const insertIdx = sortedIndexBy(childrenList, item, 'key')
childrenList.splice(insertIdx, 0, item)
// childrenList.push(item)
}
// update all node item's children num
const updateChildrenNum = (node) => {
let count = 0
const totalChildren = size(node.children)
if (totalChildren > 0) {
for (const elem of node.children) {
updateChildrenNum(elem)
count += elem.keys
}
} else {
count += 1
}
node.keys = count
// node.children = sortBy(node.children, 'label')
}
const dbs = this.databases[connName]
if (dbs[db].children == null) {
dbs[db].children = []
}
const keyStruct = dbs[db].children
for (const key in keys) {
const keyPart = split(key, separator)
// const prefixLen = size(keyPart) - 1
const len = size(keyPart)
let handlePath = ''
let ks = keyStruct
for (let i = 0; i < len; i++) {
handlePath += keyPart[i]
if (i !== len - 1) {
// layer
const curKey = `${connName}/db${db}/${handlePath}@${ConnectionType.RedisKey}`
let selectedNode = findNodeByKey(ks, curKey)
if (selectedNode == null) {
selectedNode = {
key: curKey,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
type: ConnectionType.RedisKey,
children: [],
}
sortedInsertChild(ks, selectedNode)
}
ks = selectedNode.children
handlePath += separator
} else {
// key
const curKey = `${connName}/db${db}/${handlePath}@${ConnectionType.RedisValue}`
const selectedNode = {
key: curKey,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
type: ConnectionType.RedisValue,
}
sortedInsertChild(ks, selectedNode)
}
}
}
dbs[db].opened = true
updateChildrenNum(dbs[db])
},
/**
* add key to db
* @param {string} connName
* @param {number} db
* @param {string} key
@ -521,9 +572,8 @@ const useConnectionStore = defineStore('connections', {
for (let j = 0; j < len + 1; j++) {
const treeKey = get(nodeList[j], 'key')
const isLast = j >= len - 1
const currentKey = `${connName}/db${db}/${redisKey}@${
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
}`
const keyType = isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
const currentKey = `${connName}/db${db}/${redisKey}@${keyType}`
if (treeKey > currentKey || isLast) {
// out of search range, add new item
if (isLastKeyPart) {
@ -981,9 +1031,8 @@ const useConnectionStore = defineStore('connections', {
const isLastKeyPart = i === keyLen - 1
for (let j = 0; j < len; j++) {
const treeKey = get(nodeList[j], 'key')
const currentKey = `${connName}/db${db}/${redisKey}@${
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
}`
const keyType = isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
const currentKey = `${connName}/db${db}/${redisKey}@${keyType}`
if (treeKey > currentKey) {
// out of search range, target not exists
forceBreak = true

View File

@ -1,857 +0,0 @@
import { get, isEmpty, last, remove, size, sortedIndexBy, split } from 'lodash'
import { defineStore } from 'pinia'
import {
AddHashField,
AddListItem,
AddZSetValue,
CloseConnection,
GetKeyValue,
OpenConnection,
OpenDatabase,
RemoveKey,
RenameKey,
SetHashValue,
SetKeyTTL,
SetKeyValue,
SetListItem,
SetSetItem,
UpdateSetItem,
UpdateZSetValue,
} from '../../wailsjs/go/services/connectionService.js'
import { ConnectionType } from '../consts/connection_type.js'
import useTabStore from './tab.js'
import useConnectionStore from './connections.js'
const separator = ':'
const useDatabaseStore = defineStore('database', {
/**
* @typedef {Object} DatabaseItem
* @property {string} key
* @property {string} label
* @property {string} name - server name, type != ConnectionType.Group only
* @property {number} type
* @property {number} [db] - database index, type == ConnectionType.RedisDB only
* @property {number} keys
* @property {boolean} [connected] - redis server is connected, type == ConnectionType.Server only
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
* @property {boolean} [expanded] - current node is expanded
*/
/**
*
* @returns {{connections: DatabaseItem[]}}
*/
state: () => ({
connections: [], // all connections list
databases: {}, // all database group by opened connections
}),
getters: {},
actions: {
/**
* Open connection
* @param {string} connName
* @returns {Promise<void>}
*/
async openConnection(connName) {
const { data, success, msg } = await OpenConnection(connName)
if (!success) {
throw new Error(msg)
}
// append to db node to current connection
const connStore = useConnectionStore()
const connNode = connStore.getConnection(connName)
if (connNode == null) {
throw new Error('no such connection')
}
const { db } = data
if (isEmpty(db)) {
throw new Error('no db loaded')
}
const children = []
for (let i = 0; i < db.length; i++) {
children.push({
key: `${connName}/${db[i].name}`,
label: db[i].name,
name: connName,
keys: db[i].keys,
db: i,
type: ConnectionType.RedisDB,
// isLeaf: false,
})
}
connNode.children = children
connNode.connected = true
},
/**
* Close connection
* @param {string} connName
* @returns {Promise<boolean>}
*/
async closeConnection(connName) {
const { success, msg } = await CloseConnection(connName)
if (!success) {
// throw new Error(msg)
return false
}
// successful close connection, remove all children
const connNode = this.getConnection(connName)
if (connNode == null) {
// throw new Error('no such connection')
return false
}
connNode.children = undefined
connNode.isLeaf = undefined
connNode.connected = false
connNode.expanded = false
return true
},
/**
* Get Connection by path name
* @param {string} connName
* @returns
*/
getConnection(connName) {
const conn = this.connections
for (let i = 0; i < conn.length; i++) {
if (conn[i].type === ConnectionType.Server && conn[i].key === connName) {
return conn[i]
} else if (conn[i].type === ConnectionType.Group) {
const children = conn[i].children
for (let j = 0; j < children.length; j++) {
if (children[j].type === ConnectionType.Server && conn[i].key === connName) {
return children[j]
}
}
}
}
return null
},
/**
* Open database and load all keys
* @param connName
* @param db
* @returns {Promise<void>}
*/
async openDatabase(connName, db) {
const { data, success, msg } = await OpenDatabase(connName, db)
if (!success) {
throw new Error(msg)
}
const { keys = [] } = data
if (isEmpty(keys)) {
const connNode = this.getConnection(connName)
const { children = [] } = connNode
children[db].children = []
children[db].opened = true
return
}
// insert child to children list by order
const sortedInsertChild = (childrenList, item) => {
const insertIdx = sortedIndexBy(childrenList, item, 'key')
childrenList.splice(insertIdx, 0, item)
// childrenList.push(item)
}
// update all node item's children num
const updateChildrenNum = (node) => {
let count = 0
const totalChildren = size(node.children)
if (totalChildren > 0) {
for (const elem of node.children) {
updateChildrenNum(elem)
count += elem.keys
}
} else {
count += 1
}
node.keys = count
// node.children = sortBy(node.children, 'label')
}
const keyStruct = []
const mark = {}
for (const key in keys) {
const keyPart = split(key, separator)
// const prefixLen = size(keyPart) - 1
const len = size(keyPart)
let handlePath = ''
let ks = keyStruct
for (let i = 0; i < len; i++) {
handlePath += keyPart[i]
if (i !== len - 1) {
// layer
const treeKey = `${handlePath}@${ConnectionType.RedisKey}`
if (!mark.hasOwnProperty(treeKey)) {
mark[treeKey] = {
key: `${connName}/db${db}/${treeKey}`,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
type: ConnectionType.RedisKey,
children: [],
}
sortedInsertChild(ks, mark[treeKey])
}
ks = mark[treeKey].children
handlePath += separator
} else {
// key
const treeKey = `${handlePath}@${ConnectionType.RedisValue}`
mark[treeKey] = {
key: `${connName}/db${db}/${treeKey}`,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey: handlePath,
type: ConnectionType.RedisValue,
}
sortedInsertChild(ks, mark[treeKey])
}
}
}
// append db node to current connection's children
const connNode = this.getConnection(connName)
const { children = [] } = connNode
children[db].children = keyStruct
children[db].opened = true
updateChildrenNum(children[db])
},
/**
* select node
* @param key
* @param name
* @param db
* @param type
* @param redisKey
*/
select({ key, name, db, type, redisKey }) {
if (type === ConnectionType.RedisValue) {
console.log(`[click]key:${key} db: ${db} redis key: ${redisKey}`)
// async get value for key
this.loadKeyValue(name, db, redisKey).then(() => {})
}
},
/**
* load redis key
* @param server
* @param db
* @param key
*/
async loadKeyValue(server, db, key) {
try {
const { data, success, msg } = await GetKeyValue(server, db, key)
if (success) {
const { type, ttl, value } = data
const tab = useTabStore()
tab.upsertTab({
server,
db,
type,
ttl,
key,
value,
})
} else {
console.warn('TODO: handle get key fail')
}
} finally {
}
},
/**
*
* @param {string} connName
* @param {number} db
* @param {string} key
* @private
*/
_addKey(connName, db, key) {
const connNode = this.getConnection(connName)
const { children: dbs = [] } = connNode
const dbDetail = get(dbs, db, {})
if (dbDetail == null) {
return
}
const descendantChain = [dbDetail]
const keyPart = split(key, separator)
let redisKey = ''
const keyLen = size(keyPart)
let added = false
for (let i = 0; i < keyLen; i++) {
redisKey += keyPart[i]
const node = last(descendantChain)
const nodeList = get(node, 'children', [])
const len = size(nodeList)
const isLastKeyPart = i === keyLen - 1
for (let j = 0; j < len + 1; j++) {
const treeKey = get(nodeList[j], 'key')
const isLast = j >= len - 1
const currentKey = `${connName}/db${db}/${redisKey}@${
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
}`
if (treeKey > currentKey || isLast) {
// out of search range, add new item
if (isLastKeyPart) {
// key not exists, add new one
const item = {
key: currentKey,
label: keyPart[i],
name: connName,
db,
keys: 1,
redisKey,
type: ConnectionType.RedisValue,
}
if (isLast) {
nodeList.push(item)
} else {
nodeList.splice(j, 0, item)
}
added = true
} else {
// layer not exists, add new one
const item = {
key: currentKey,
label: keyPart[i],
name: connName,
db,
keys: 0,
redisKey,
type: ConnectionType.RedisKey,
children: [],
}
if (isLast) {
nodeList.push(item)
descendantChain.push(last(nodeList))
} else {
nodeList.splice(j, 0, item)
descendantChain.push(nodeList[j])
}
redisKey += separator
added = true
}
break
} else if (treeKey === currentKey) {
if (isLastKeyPart) {
// same key exists, do nothing
console.log('TODO: same key exist, do nothing now, should replace value later')
} else {
// same group exists, find into it's children
descendantChain.push(nodeList[j])
redisKey += separator
}
break
}
}
}
// update ancestor node's info
if (added) {
const desLen = size(descendantChain)
for (let i = 0; i < desLen; i++) {
const children = get(descendantChain[i], 'children', [])
let keys = 0
for (const child of children) {
if (child.type === ConnectionType.RedisKey) {
keys += get(child, 'keys', 1)
} else if (child.type === ConnectionType.RedisValue) {
keys += get(child, 'keys', 0)
}
}
descendantChain[i].keys = keys
}
}
},
/**
* set redis key
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {number} keyType
* @param {any} value
* @param {number} ttl
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
async setKey(connName, db, key, keyType, value, ttl) {
try {
const { data, success, msg } = await SetKeyValue(connName, db, key, keyType, value, ttl)
if (success) {
// update tree view data
this._addKey(connName, db, key)
return { success }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* update hash field
* when field is set, newField is null, delete field
* when field is null, newField is set, add new field
* when both field and newField are set, and field === newField, update field
* when both field and newField are set, and field !== newField, delete field and add newField
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string} field
* @param {string} newField
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
*/
async setHash(connName, db, key, field, newField, value) {
try {
const { data, success, msg } = await SetHashValue(connName, db, key, field, newField || '', value || '')
if (success) {
const { updated = {} } = data
return { success, updated }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* insert or update hash field item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {number }action 0:ignore duplicated fields 1:overwrite duplicated fields
* @param {string[]} fieldItems field1, value1, filed2, value2...
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
*/
async addHashField(connName, db, key, action, fieldItems) {
try {
const { data, success, msg } = await AddHashField(connName, db, key, action, fieldItems)
if (success) {
const { updated = {} } = data
return { success, updated }
} else {
return { success: false, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* remove hash field
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string} field
* @returns {Promise<{[msg]: {}, success: boolean, [removed]: string[]}>}
*/
async removeHashField(connName, db, key, field) {
try {
const { data, success, msg } = await SetHashValue(connName, db, key, field, '', '')
if (success) {
const { removed = [] } = data
return { success, removed }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* insert list item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {int} action 0: push to head, 1: push to tail
* @param {string[]}values
* @returns {Promise<*|{msg, success: boolean}>}
*/
async addListItem(connName, db, key, action, values) {
try {
return AddListItem(connName, db, key, action, values)
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* prepend item to head of list
* @param connName
* @param db
* @param key
* @param values
* @returns {Promise<[msg]: string, success: boolean, [item]: []>}
*/
async prependListItem(connName, db, key, values) {
try {
const { data, success, msg } = await AddListItem(connName, db, key, 0, values)
if (success) {
const { left = [] } = data
return { success, item: left }
} else {
return { success: false, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* append item to tail of list
* @param connName
* @param db
* @param key
* @param values
* @returns {Promise<[msg]: string, success: boolean, [item]: any[]>}
*/
async appendListItem(connName, db, key, values) {
try {
const { data, success, msg } = await AddListItem(connName, db, key, 1, values)
if (success) {
const { right = [] } = data
return { success, item: right }
} else {
return { success: false, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* update value of list item by index
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {number} index
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
*/
async updateListItem(connName, db, key, index, value) {
try {
const { data, success, msg } = await SetListItem(connName, db, key, index, value)
if (success) {
const { updated = {} } = data
return { success, updated }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* remove list item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {number} index
* @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>}
*/
async removeListItem(connName, db, key, index) {
try {
const { data, success, msg } = await SetListItem(connName, db, key, index, '')
if (success) {
const { removed = [] } = data
return { success, removed }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* add item to set
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
async addSetItem(connName, db, key, value) {
try {
const { success, msg } = await SetSetItem(connName, db, key, false, [value])
if (success) {
return { success }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* update value of set item
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string} value
* @param {string} newValue
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
async updateSetItem(connName, db, key, value, newValue) {
try {
const { success, msg } = await UpdateSetItem(connName, db, key, value, newValue)
if (success) {
return { success: true }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* remove item from set
* @param connName
* @param db
* @param key
* @param value
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
async removeSetItem(connName, db, key, value) {
try {
const { success, msg } = await SetSetItem(connName, db, key, true, [value])
if (success) {
return { success }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* add item to sorted set
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {number} action
* @param {Object.<string, number>} vs value: score
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
async addZSetItem(connName, db, key, action, vs) {
try {
const { success, msg } = await AddZSetValue(connName, db, key, action, vs)
if (success) {
return { success }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* update item of sorted set
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string} value
* @param {string} newValue
* @param {number} score
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}, [removed]: []}>}
*/
async updateZSetItem(connName, db, key, value, newValue, score) {
try {
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, newValue, score)
if (success) {
const { updated, removed } = data
return { success, updated, removed }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* remove item from sorted set
* @param {string} connName
* @param {number} db
* @param key
* @param {string} value
* @returns {Promise<{[msg]: string, success: boolean, [removed]: []}>}
*/
async removeZSetItem(connName, db, key, value) {
try {
const { data, success, msg } = await UpdateZSetValue(connName, db, key, value, '', 0)
if (success) {
const { removed } = data
return { success, removed }
} else {
return { success, msg }
}
} catch (e) {
return { success: false, msg: e.message }
}
},
/**
* reset key's ttl
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {number} ttl
* @returns {Promise<boolean>}
*/
async setTTL(connName, db, key, ttl) {
try {
const { success, msg } = await SetKeyTTL(connName, db, key, ttl)
return success === true
} catch (e) {
return false
}
},
/**
*
* @param {string} connName
* @param {number} db
* @param {string} key
* @private
*/
_removeKey(connName, db, key) {
const connNode = this.getConnection(connName)
const { children: dbs = [] } = connNode
const dbDetail = get(dbs, db, {})
if (dbDetail == null) {
return
}
const descendantChain = [dbDetail]
const keyPart = split(key, separator)
let redisKey = ''
const keyLen = size(keyPart)
let deleted = false
let forceBreak = false
for (let i = 0; i < keyLen && !forceBreak; i++) {
redisKey += keyPart[i]
const node = last(descendantChain)
const nodeList = get(node, 'children', [])
const len = size(nodeList)
const isLastKeyPart = i === keyLen - 1
for (let j = 0; j < len; j++) {
const treeKey = get(nodeList[j], 'key')
const currentKey = `${connName}/db${db}/${redisKey}@${
isLastKeyPart ? ConnectionType.RedisValue : ConnectionType.RedisKey
}`
if (treeKey > currentKey) {
// out of search range, target not exists
forceBreak = true
break
} else if (treeKey === currentKey) {
if (isLastKeyPart) {
// find target
nodeList.splice(j, 1)
node.keys -= 1
deleted = true
forceBreak = true
} else {
// find into it's children
descendantChain.push(nodeList[j])
redisKey += separator
}
break
}
}
if (forceBreak) {
break
}
}
// console.log(JSON.stringify(descendantChain))
// update ancestor node's info
if (deleted) {
const desLen = size(descendantChain)
for (let i = desLen - 1; i > 0; i--) {
const children = get(descendantChain[i], 'children', [])
const parent = descendantChain[i - 1]
if (isEmpty(children)) {
const parentChildren = get(parent, 'children', [])
const k = get(descendantChain[i], 'key')
remove(parentChildren, (item) => item.key === k)
}
parent.keys -= 1
}
}
},
/**
* remove redis key
* @param {string} connName
* @param {number} db
* @param {string} key
* @returns {Promise<boolean>}
*/
async removeKey(connName, db, key) {
try {
const { data, success, msg } = await RemoveKey(connName, db, key)
if (success) {
// update tree view data
this._removeKey(connName, db, key)
// set tab content empty
const tab = useTabStore()
tab.emptyTab(connName)
return true
}
} finally {
}
return false
},
/**
* rename key
* @param {string} connName
* @param {number} db
* @param {string} key
* @param {string} newKey
* @returns {Promise<{[msg]: string, success: boolean}>}
*/
async renameKey(connName, db, key, newKey) {
const { success = false, msg } = await RenameKey(connName, db, key, newKey)
if (success) {
// delete old key and add new key struct
this._removeKey(connName, db, key)
this._addKey(connName, db, newKey)
return { success: true }
} else {
return { success: false, msg }
}
},
},
})
export default useDatabaseStore

14
go.mod
View File

@ -3,7 +3,7 @@ module tinyrdm
go 1.20
require (
github.com/bytedance/sonic v1.9.1
github.com/bytedance/sonic v1.9.2
github.com/google/go-cmp v0.5.9
github.com/redis/go-redis/v9 v9.0.5
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68
@ -22,7 +22,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
@ -38,11 +38,11 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
)
// replace github.com/wailsapp/wails/v2 v2.5.1 => /Users/lykin/go/pkg/mod

28
go.sum
View File

@ -3,8 +3,8 @@ github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3IS
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/bytedance/sonic v1.9.2 h1:GDaNjuWSGu09guE9Oql0MSTNhNCLlWwO8y/xM5BzcbM=
github.com/bytedance/sonic v1.9.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
@ -32,8 +32,8 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA=
github.com/leaanthony/go-ansi-parser v1.6.0 h1:T8TuMhFB6TUMIUm0oRrSbgJudTFw9csT3ZK09w0t4Pg=
github.com/leaanthony/go-ansi-parser v1.6.0/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ=
github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
@ -88,13 +88,13 @@ github.com/wailsapp/wails/v2 v2.5.1/go.mod h1:jbOZbcr/zm79PxXxAjP8UoVlDd9wLW3uDs
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -106,12 +106,12 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=