refactor: encapsulate connection state and behavior into classes
This commit is contained in:
parent
7fa1ecfa0a
commit
5b9f261824
|
@ -144,7 +144,7 @@ func (b *browserService) OpenConnection(name string) (resp types.JSResp) {
|
|||
{
|
||||
Name: "db0",
|
||||
Index: 0,
|
||||
Keys: int(clusterKeyCount),
|
||||
MaxKeys: int(clusterKeyCount),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
|
@ -179,7 +179,7 @@ func (b *browserService) OpenConnection(name string) (resp types.JSResp) {
|
|||
return types.ConnectionDB{
|
||||
Name: dbName,
|
||||
Index: idx,
|
||||
Keys: dbInfo["keys"],
|
||||
MaxKeys: dbInfo["keys"],
|
||||
Expires: dbInfo["expires"],
|
||||
AvgTTL: dbInfo["avg_ttl"],
|
||||
}
|
||||
|
@ -454,7 +454,7 @@ func (b *browserService) scanKeys(ctx context.Context, client redis.UniversalCli
|
|||
return nil
|
||||
}
|
||||
|
||||
var keys []any
|
||||
keys := make([]any, 0)
|
||||
if cluster, ok := client.(*redis.ClusterClient); ok {
|
||||
// cluster mode
|
||||
var mutex sync.Mutex
|
||||
|
@ -472,7 +472,7 @@ func (b *browserService) scanKeys(ctx context.Context, client redis.UniversalCli
|
|||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, cursor, err
|
||||
return keys, cursor, err
|
||||
}
|
||||
return keys, cursor, nil
|
||||
}
|
||||
|
@ -778,6 +778,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
if param.Full || matchPattern != "*" {
|
||||
// load all
|
||||
cursor, reset = 0, true
|
||||
items = []types.HashEntryItem{}
|
||||
for {
|
||||
loadedVal, cursor, subErr = client.HScan(ctx, key, cursor, matchPattern, scanSize).Result()
|
||||
if subErr != nil {
|
||||
|
@ -848,6 +849,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
if param.Full || matchPattern != "*" {
|
||||
// load all
|
||||
cursor, reset = 0, true
|
||||
items = []types.SetEntryItem{}
|
||||
for {
|
||||
loadedKey, cursor, subErr = client.SScan(ctx, key, cursor, matchPattern, scanSize).Result()
|
||||
if subErr != nil {
|
||||
|
@ -912,6 +914,7 @@ func (b *browserService) GetKeyDetail(param types.KeyDetailParam) (resp types.JS
|
|||
// load all
|
||||
var loadedVal []string
|
||||
cursor, reset = 0, true
|
||||
items = []types.ZSetEntryItem{}
|
||||
for {
|
||||
loadedVal, cursor, err = client.ZScan(ctx, key, cursor, matchPattern, scanSize).Result()
|
||||
if err != nil {
|
||||
|
@ -1158,7 +1161,7 @@ func (b *browserService) SetKeyValue(param types.SetKeyParam) (resp types.JSResp
|
|||
score, _ := strconv.ParseFloat(strs[i+1].(string), 64)
|
||||
members = append(members, redis.Z{
|
||||
Score: score,
|
||||
Member: strs[i],
|
||||
Member: strs[i].(string),
|
||||
})
|
||||
}
|
||||
err = client.ZAdd(ctx, key, members...).Err()
|
||||
|
|
|
@ -36,7 +36,7 @@ type Connections []Connection
|
|||
type ConnectionDB struct {
|
||||
Name string `json:"name"`
|
||||
Index int `json:"index"`
|
||||
Keys int `json:"keys"`
|
||||
MaxKeys int `json:"maxKeys"`
|
||||
Expires int `json:"expires,omitempty"`
|
||||
AvgTTL int `json:"avgTtl,omitempty"`
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { computed, h, reactive, ref, watch } from 'vue'
|
||||
import { types, typesColor } from '@/consts/support_redis_type.js'
|
||||
import useDialog from 'stores/dialog'
|
||||
import { get, isEmpty, keys, map, trim } from 'lodash'
|
||||
import { endsWith, get, isEmpty, keys, map, trim } from 'lodash'
|
||||
import NewStringValue from '@/components/new_value/NewStringValue.vue'
|
||||
import NewHashValue from '@/components/new_value/NewHashValue.vue'
|
||||
import NewListValue from '@/components/new_value/NewListValue.vue'
|
||||
|
@ -32,7 +32,7 @@ const formRules = computed(() => {
|
|||
}
|
||||
})
|
||||
const dbOptions = computed(() =>
|
||||
map(keys(browserStore.databases[newForm.server]), (key) => ({
|
||||
map(keys(browserStore.getDBList(newForm.server)), (key) => ({
|
||||
label: key,
|
||||
value: parseInt(key),
|
||||
})),
|
||||
|
@ -69,8 +69,17 @@ watch(
|
|||
(visible) => {
|
||||
if (visible) {
|
||||
const { prefix, server, db } = dialogStore.newKeyParam
|
||||
const separator = browserStore.getSeparator(server)
|
||||
newForm.server = server
|
||||
newForm.key = isEmpty(prefix) ? '' : prefix
|
||||
if (isEmpty(prefix)) {
|
||||
newForm.key = ''
|
||||
} else {
|
||||
if (!endsWith(prefix, separator)) {
|
||||
newForm.key = prefix + separator
|
||||
} else {
|
||||
newForm.key = prefix
|
||||
}
|
||||
}
|
||||
newForm.db = db
|
||||
newForm.type = options.value[0].value
|
||||
newForm.ttl = -1
|
||||
|
|
|
@ -4,7 +4,7 @@ import BrowserTree from './BrowserTree.vue'
|
|||
import IconButton from '@/components/common/IconButton.vue'
|
||||
import useTabStore from 'stores/tab.js'
|
||||
import { computed, nextTick, onMounted, reactive, ref, unref } from 'vue'
|
||||
import { find, map, size } from 'lodash'
|
||||
import { find, get, map, size } from 'lodash'
|
||||
import Refresh from '@/components/icons/Refresh.vue'
|
||||
import useDialogStore from 'stores/dialog.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
@ -24,6 +24,7 @@ import ListCheckbox from '@/components/icons/ListCheckbox.vue'
|
|||
import Close from '@/components/icons/Close.vue'
|
||||
import More from '@/components/icons/More.vue'
|
||||
import Export from '@/components/icons/Export.vue'
|
||||
import { ConnectionType } from '@/consts/connection_type.js'
|
||||
|
||||
const props = defineProps({
|
||||
server: String,
|
||||
|
@ -51,7 +52,7 @@ const dbSelectOptions = computed(() => {
|
|||
if (props.db === db.db) {
|
||||
return {
|
||||
value: db.db,
|
||||
label: `db${db.db} (${db.keys}/${db.maxKeys})`,
|
||||
label: `db${db.db} (${db.keyCount}/${db.maxKeys})`,
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
@ -77,7 +78,7 @@ const loadProgress = computed(() => {
|
|||
if (db.maxKeys <= 0) {
|
||||
return 100
|
||||
}
|
||||
return (db.keys * 100) / Math.max(db.keys, db.maxKeys)
|
||||
return (db.keyCount * 100) / Math.max(db.keyCount, db.maxKeys)
|
||||
})
|
||||
|
||||
const checkedCount = computed(() => {
|
||||
|
@ -87,7 +88,7 @@ const checkedCount = computed(() => {
|
|||
const checkedTip = computed(() => {
|
||||
const dblist = browserStore.getDBList(props.server)
|
||||
const db = find(dblist, { db: props.db })
|
||||
return `${checkedCount.value} / ${Math.max(db.maxKeys, checkedCount.value)}`
|
||||
return `${checkedCount.value} / ${Math.max(db.keyCount, checkedCount.value)}`
|
||||
})
|
||||
|
||||
const onReload = async () => {
|
||||
|
@ -117,6 +118,16 @@ const onReload = async () => {
|
|||
}
|
||||
|
||||
const onAddKey = () => {
|
||||
const selectedKey = get(browserTreeRef.value?.getSelectedKey(), 0)
|
||||
if (selectedKey != null) {
|
||||
const node = browserStore.getNode(selectedKey)
|
||||
const { type = ConnectionType.RedisValue, redisKey } = node
|
||||
if (type === ConnectionType.RedisKey) {
|
||||
// has prefix
|
||||
dialogStore.openNewKeyDialog(redisKey, props.server, props.db)
|
||||
return
|
||||
}
|
||||
}
|
||||
dialogStore.openNewKeyDialog('', props.server, props.db)
|
||||
}
|
||||
|
||||
|
|
|
@ -423,7 +423,7 @@ const renderPrefix = ({ option }) => {
|
|||
const renderLabel = ({ option }) => {
|
||||
switch (option.type) {
|
||||
case ConnectionType.RedisKey:
|
||||
return `${option.label} (${option.keys || 0})`
|
||||
return `${option.label} (${option.keyCount || 0})`
|
||||
// case ConnectionType.RedisValue:
|
||||
// return `[${option.keyType}]${option.label}`
|
||||
}
|
||||
|
@ -588,6 +588,9 @@ defineExpose({
|
|||
dialogStore.openExportKeyDialog(props.server, props.db, redisKeys)
|
||||
}
|
||||
},
|
||||
getSelectedKey: () => {
|
||||
return selectedKeys.value || []
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* redis database item
|
||||
*/
|
||||
export class RedisDatabaseItem {
|
||||
constructor({ db = 0, keyCount = 0, maxKeys = 0 }) {
|
||||
this.db = db
|
||||
this.keyCount = keyCount
|
||||
this.maxKeys = maxKeys
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
import { isEmpty, remove, sortedIndexBy, sumBy } from 'lodash'
|
||||
import { ConnectionType } from '@/consts/connection_type.js'
|
||||
|
||||
/**
|
||||
* redis node item in tree view
|
||||
*/
|
||||
export class RedisNodeItem {
|
||||
/**
|
||||
*
|
||||
* @param {string} key - tree node unique key
|
||||
* @param {string} label
|
||||
* @param {string} [name] - server name, type != ConnectionType.Group only
|
||||
* @param {ConnectionType} type
|
||||
* @param {number} [db] - database index, type == ConnectionType.RedisDB only
|
||||
* @param {string} [redisKey] - redis key, type == ConnectionType.RedisKey || type == ConnectionType.RedisValue only
|
||||
* @param {number[]} [redisKeyCode] - redis key char code array, optional for redis key which contains binary data
|
||||
* @param {number} [keyCount] - children key count
|
||||
* @param {number} [maxKeys] - max key count for database
|
||||
* @param {boolean} [isLeaf]
|
||||
* @param {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
|
||||
* @param {boolean} [expanded] - current node is expanded
|
||||
* @param {RedisNodeItem[]} [children]
|
||||
* @param {string} [redisType] - redis type name, 'loading' indicate that is in loading progress
|
||||
*/
|
||||
constructor({
|
||||
key,
|
||||
label,
|
||||
name,
|
||||
type,
|
||||
db = 0,
|
||||
redisKey,
|
||||
redisKeyCode,
|
||||
keyCount = 0,
|
||||
maxKeys = 0,
|
||||
isLeaf = false,
|
||||
opened = false,
|
||||
expanded = false,
|
||||
children,
|
||||
redisType,
|
||||
}) {
|
||||
this.key = key
|
||||
this.label = label
|
||||
this.name = name
|
||||
this.type = type
|
||||
this.db = db
|
||||
this.redisKey = redisKey
|
||||
this.redisKeyCode = redisKeyCode
|
||||
this.keyCount = keyCount
|
||||
this.maxKeys = maxKeys
|
||||
this.isLeaf = isLeaf
|
||||
this.opened = opened
|
||||
this.expanded = expanded
|
||||
this.children = children
|
||||
this.redisType = redisType
|
||||
}
|
||||
|
||||
/**
|
||||
* sort node list
|
||||
* @param {RedisNodeItem[]} nodeList
|
||||
* @private
|
||||
*/
|
||||
_sortNodes(nodeList) {
|
||||
if (nodeList == null) {
|
||||
return
|
||||
}
|
||||
nodeList.sort((a, b) => {
|
||||
return a.key > b.key ? 1 : -1
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* sort all node item's children and calculate keys count
|
||||
* @param skipSort skip sorting children
|
||||
* @returns {boolean} return whether key count changed
|
||||
*/
|
||||
tidy(skipSort) {
|
||||
let count = 0
|
||||
if (!isEmpty(this.children)) {
|
||||
if (skipSort !== true) {
|
||||
this._sortNodes(this.children)
|
||||
}
|
||||
|
||||
for (const elem of this.children) {
|
||||
elem.tidy(skipSort)
|
||||
count += elem.keyCount
|
||||
}
|
||||
} else {
|
||||
if (this.type === ConnectionType.RedisValue) {
|
||||
count += 1
|
||||
} else {
|
||||
// no children in db node or layer node, set count to 0
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
if (this.keyCount !== count) {
|
||||
this.keyCount = count
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RedisNodeItem} child
|
||||
* @param {boolean} [sorted]
|
||||
*/
|
||||
addChild(child, sorted) {
|
||||
if (!!!sorted) {
|
||||
this.children.push(child)
|
||||
} else {
|
||||
const idx = sortedIndexBy(this.children, child, 'key')
|
||||
this.children.splice(idx, 0, child)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{}} predicate
|
||||
*/
|
||||
removeChild(predicate) {
|
||||
if (this.type !== ConnectionType.RedisKey) {
|
||||
return
|
||||
}
|
||||
const removed = remove(this.children, predicate)
|
||||
if (this.children.length <= 0) {
|
||||
// remove from parent if no children
|
||||
}
|
||||
}
|
||||
|
||||
getChildren() {
|
||||
return this.children
|
||||
}
|
||||
|
||||
calcKeyCount() {
|
||||
if (this.type === ConnectionType.RedisValue) {
|
||||
return 1
|
||||
}
|
||||
return sumBy(this.children, (c) => c.keyCount)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,441 @@
|
|||
import { initial, isEmpty, join, last, mapValues, size, slice, sortBy, split, sumBy, toUpper } from 'lodash'
|
||||
import useConnectionStore from 'stores/connections.js'
|
||||
import { ConnectionType } from '@/consts/connection_type.js'
|
||||
import { RedisDatabaseItem } from '@/objects/redisDatabaseItem.js'
|
||||
import { KeyViewType } from '@/consts/key_view_type.js'
|
||||
import { RedisNodeItem } from '@/objects/redisNodeItem.js'
|
||||
import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js'
|
||||
|
||||
/**
|
||||
* server connection state
|
||||
*/
|
||||
export class RedisServerState {
|
||||
/**
|
||||
* @typedef {Object} LoadingState
|
||||
* @property {boolean} loading indicated that is loading children now
|
||||
* @property {boolean} fullLoaded indicated that all children already loaded
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} name server name
|
||||
* @param {number} db current opened database
|
||||
* @param {{}} stats current server status info
|
||||
* @param {Object.<number, RedisDatabaseItem>} databases database list
|
||||
* @param {string|null} patternFilter pattern filter
|
||||
* @param {string|null} typeFilter redis type filter
|
||||
* @param {LoadingState} loadingState all loading state in opened connections map by server and LoadingState
|
||||
* @param {KeyViewType} viewType view type selection for all opened connections group by 'server'
|
||||
* @param {Map<string, RedisNodeItem>} nodeMap map nodes by "key#type"
|
||||
*/
|
||||
constructor({
|
||||
name,
|
||||
db = 0,
|
||||
stats = {},
|
||||
databases = {},
|
||||
patternFilter = null,
|
||||
typeFilter = null,
|
||||
loadingState = {},
|
||||
viewType = KeyViewType.Tree,
|
||||
nodeMap = new Map(),
|
||||
}) {
|
||||
this.name = name
|
||||
this.db = db
|
||||
this.stats = stats
|
||||
this.databases = databases
|
||||
this.patternFilter = patternFilter
|
||||
this.typeFilter = typeFilter
|
||||
this.loadingState = loadingState
|
||||
this.viewType = viewType
|
||||
this.nodeMap = nodeMap
|
||||
this.getRoot()
|
||||
|
||||
const connStore = useConnectionStore()
|
||||
const { keySeparator } = connStore.getDefaultSeparator(name)
|
||||
this.separator = isEmpty(keySeparator) ? ':' : keySeparator
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.stats = {}
|
||||
this.patternFilter = null
|
||||
this.typeFilter = null
|
||||
this.nodeMap.clear()
|
||||
}
|
||||
|
||||
closeDatabase() {
|
||||
this.patternFilter = null
|
||||
this.typeFilter = null
|
||||
this.nodeMap.clear()
|
||||
}
|
||||
|
||||
setDatabaseKeyCount(db, maxKeys) {
|
||||
const dbInst = this.databases[db]
|
||||
if (dbInst == null) {
|
||||
this.databases[db] = new RedisDatabaseItem({ db, maxKeys })
|
||||
} else {
|
||||
dbInst.maxKeys = maxKeys
|
||||
}
|
||||
return dbInst
|
||||
}
|
||||
|
||||
/**
|
||||
* update max key by increase/decrease value
|
||||
* @param {string} db
|
||||
* @param {number} updateVal
|
||||
*/
|
||||
updateDBKeyCount(db, updateVal) {
|
||||
const dbInst = this.databases[this.db]
|
||||
if (dbInst != null) {
|
||||
dbInst.maxKeys = Math.max(0, dbInst.maxKeys + updateVal)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set db max keys value
|
||||
* @param {number} db
|
||||
* @param {number} count
|
||||
*/
|
||||
setDBKeyCount(db, count) {
|
||||
const dbInst = this.databases[db]
|
||||
if (dbInst != null) {
|
||||
dbInst.maxKeys = Math.max(0, count)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get tree root item
|
||||
* @returns {RedisNodeItem}
|
||||
*/
|
||||
getRoot() {
|
||||
const rootKey = `${ConnectionType.RedisDB}`
|
||||
let root = this.nodeMap.get(rootKey)
|
||||
if (root == null) {
|
||||
// create root node
|
||||
root = new RedisNodeItem({
|
||||
key: rootKey,
|
||||
label: this.separator,
|
||||
type: ConnectionType.RedisDB,
|
||||
children: [],
|
||||
})
|
||||
this.nodeMap.set(rootKey, root)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
/**
|
||||
* get database list sort by db asc
|
||||
* @return {RedisDatabaseItem[]}
|
||||
*/
|
||||
getDatabase() {
|
||||
return sortBy(mapValues(this.databases), 'db')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ConnectionType} type
|
||||
* @param {string} keyPath
|
||||
* @param {RedisNodeItem} node
|
||||
*/
|
||||
addNode(type, keyPath, node) {
|
||||
this.nodeMap.set(`${type}/${keyPath}`, node)
|
||||
}
|
||||
|
||||
/**
|
||||
* add keys to current opened database
|
||||
* @param {Array<string|number[]>|Set<string|number[]>} keys
|
||||
* @param {boolean} [sortInsert]
|
||||
* @return {{newKey: number, newLayer: number, success: boolean, replaceKey: number}}
|
||||
*/
|
||||
addKeyNodes(keys, sortInsert) {
|
||||
const result = {
|
||||
success: false,
|
||||
newLayer: 0,
|
||||
newKey: 0,
|
||||
replaceKey: 0,
|
||||
}
|
||||
const root = this.getRoot()
|
||||
|
||||
if (this.viewType === KeyViewType.List) {
|
||||
// construct list view data
|
||||
for (const key of keys) {
|
||||
const k = decodeRedisKey(key)
|
||||
const isBinaryKey = k !== key
|
||||
const nodeKey = `${ConnectionType.RedisValue}/${nativeRedisKey(key)}`
|
||||
const replaceKey = this.nodeMap.has(nodeKey)
|
||||
const selectedNode = new RedisNodeItem({
|
||||
key: `${this.name}/db${this.db}#${nodeKey}`,
|
||||
label: k,
|
||||
db: this.db,
|
||||
keyCount: 0,
|
||||
redisKey: k,
|
||||
redisKeyCode: isBinaryKey ? key : undefined,
|
||||
redisKeyType: undefined,
|
||||
type: ConnectionType.RedisValue,
|
||||
isLeaf: true,
|
||||
})
|
||||
this.nodeMap.set(nodeKey, selectedNode)
|
||||
if (!replaceKey) {
|
||||
root.addChild(selectedNode, sortInsert)
|
||||
result.newKey += 1
|
||||
} else {
|
||||
result.replaceKey += 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// construct tree view data
|
||||
for (const key of keys) {
|
||||
const k = decodeRedisKey(key)
|
||||
const isBinaryKey = k !== key
|
||||
const keyParts = isBinaryKey ? [nativeRedisKey(key)] : split(k, this.separator)
|
||||
const len = size(keyParts)
|
||||
const lastIdx = len - 1
|
||||
let handlePath = ''
|
||||
let node = root
|
||||
for (let i = 0; i < len; i++) {
|
||||
handlePath += keyParts[i]
|
||||
if (i !== lastIdx) {
|
||||
// layer
|
||||
const nodeKey = `${ConnectionType.RedisKey}/${handlePath}`
|
||||
let selectedNode = this.nodeMap.get(nodeKey)
|
||||
if (selectedNode == null) {
|
||||
selectedNode = new RedisNodeItem({
|
||||
key: `${this.name}/db${this.db}#${nodeKey}`,
|
||||
label: keyParts[i],
|
||||
db: this.db,
|
||||
keyCount: 0,
|
||||
redisKey: handlePath,
|
||||
type: ConnectionType.RedisKey,
|
||||
isLeaf: false,
|
||||
children: [],
|
||||
})
|
||||
this.nodeMap.set(nodeKey, selectedNode)
|
||||
node.addChild(selectedNode, sortInsert)
|
||||
result.newLayer += 1
|
||||
}
|
||||
node = selectedNode
|
||||
handlePath += this.separator
|
||||
} else {
|
||||
// key
|
||||
const nodeKey = `${ConnectionType.RedisValue}/${handlePath}`
|
||||
const replaceKey = this.nodeMap.has(nodeKey)
|
||||
const selectedNode = new RedisNodeItem({
|
||||
key: `${this.name}/db${this.db}#${nodeKey}`,
|
||||
label: isBinaryKey ? k : keyParts[i],
|
||||
db: this.db,
|
||||
keyCount: 0,
|
||||
redisKey: handlePath,
|
||||
redisKeyCode: isBinaryKey ? key : undefined,
|
||||
redisKeyType: undefined,
|
||||
type: ConnectionType.RedisValue,
|
||||
isLeaf: true,
|
||||
})
|
||||
this.nodeMap.set(nodeKey, selectedNode)
|
||||
if (!replaceKey) {
|
||||
node.addChild(selectedNode, sortInsert)
|
||||
result.newKey += 1
|
||||
} else {
|
||||
result.replaceKey += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* rename key to a new name
|
||||
* @param key
|
||||
* @param newKey
|
||||
*/
|
||||
renameKey(key, newKey) {
|
||||
const oldLayer = initial(key.split(this.separator)).join(this.separator)
|
||||
const newLayer = initial(newKey.split(this.separator)).join(this.separator)
|
||||
if (oldLayer !== newLayer) {
|
||||
// also change layer
|
||||
this.removeKeyNode(key, false)
|
||||
const { success } = this.addKeyNodes([newKey], true)
|
||||
if (success) {
|
||||
this.tidyNode(newLayer)
|
||||
}
|
||||
} else {
|
||||
// change key name only
|
||||
const oldNodeKeyName = `${ConnectionType.RedisValue}/${key}`
|
||||
const newNodeKeyName = `${ConnectionType.RedisValue}/${newKey}`
|
||||
const keyNode = this.nodeMap.get(oldNodeKeyName)
|
||||
keyNode.key = `${this.name}/db${this.db}#${newNodeKeyName}`
|
||||
keyNode.label = last(split(newKey, this.separator))
|
||||
keyNode.redisKey = newKey
|
||||
// not support rename binary key name yet
|
||||
// keyNode.redisKeyCode = []
|
||||
this.nodeMap.set(newNodeKeyName, keyNode)
|
||||
this.nodeMap.delete(oldNodeKeyName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove key node by key name
|
||||
* @param {string} [key]
|
||||
* @param {boolean} [isLayer]
|
||||
* @return {boolean}
|
||||
*/
|
||||
removeKeyNode(key, isLayer) {
|
||||
if (isLayer === true) {
|
||||
this.deleteChildrenKeyNodes(key)
|
||||
}
|
||||
|
||||
const dbRoot = this.getRoot()
|
||||
if (isEmpty(key)) {
|
||||
// clear all key nodes
|
||||
this.nodeMap.clear()
|
||||
this.getRoot()
|
||||
} else {
|
||||
const keyParts = split(key, this.separator)
|
||||
const totalParts = size(keyParts)
|
||||
// remove from parent in tree node
|
||||
const parentKey = slice(keyParts, 0, totalParts - 1)
|
||||
let parentNode
|
||||
if (isEmpty(parentKey)) {
|
||||
parentNode = dbRoot
|
||||
} else {
|
||||
parentNode = this.nodeMap.get(`${ConnectionType.RedisKey}/${join(parentKey, this.separator)}`)
|
||||
}
|
||||
|
||||
// not found parent node
|
||||
if (parentNode == null) {
|
||||
return false
|
||||
}
|
||||
parentNode.removeChild({
|
||||
type: isLayer ? ConnectionType.RedisKey : ConnectionType.RedisValue,
|
||||
redisKey: key,
|
||||
})
|
||||
|
||||
// check and remove empty layer node
|
||||
let i = totalParts - 1
|
||||
for (; i >= 0; i--) {
|
||||
const anceKey = join(slice(keyParts, 0, i), this.separator)
|
||||
if (i > 0) {
|
||||
const anceNode = this.nodeMap.get(`${ConnectionType.RedisKey}/${anceKey}`)
|
||||
const redisKey = join(slice(keyParts, 0, i + 1), this.separator)
|
||||
anceNode.removeChild({ type: ConnectionType.RedisKey, redisKey })
|
||||
|
||||
if (isEmpty(anceNode.children)) {
|
||||
this.nodeMap.delete(`${ConnectionType.RedisKey}/${anceKey}`)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// last one, remove from db node
|
||||
dbRoot.removeChild({ type: ConnectionType.RedisKey, redisKey: keyParts[0] })
|
||||
this.nodeMap.delete(`${ConnectionType.RedisValue}/${keyParts[0]}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* tidy node by key
|
||||
* @param {string} [key]
|
||||
* @param {boolean} [skipResort]
|
||||
* @return
|
||||
*/
|
||||
tidyNode(key, skipResort) {
|
||||
const dbNode = this.getRoot()
|
||||
const keyParts = split(key, this.separator)
|
||||
const totalParts = size(keyParts)
|
||||
let node
|
||||
// find last exists ancestor key
|
||||
let i = totalParts - 1
|
||||
for (; i > 0; i--) {
|
||||
const parentKey = join(slice(keyParts, 0, i), this.separator)
|
||||
node = this.nodeMap.get(`${ConnectionType.RedisKey}/${parentKey}`)
|
||||
if (node != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (node == null) {
|
||||
node = dbNode
|
||||
}
|
||||
const keyCountUpdated = node.tidy(skipResort)
|
||||
|
||||
if (keyCountUpdated) {
|
||||
// update key count of parent and above
|
||||
for (; i > 0; i--) {
|
||||
const parentKey = join(slice(keyParts, 0, i), this.separator)
|
||||
const parentNode = this.nodeMap.get(`${ConnectionType.RedisKey}/${parentKey}`)
|
||||
if (parentNode == null) {
|
||||
break
|
||||
}
|
||||
parentNode.keyCount = parentNode.calcKeyCount()
|
||||
}
|
||||
// update key count of db
|
||||
// dbNode.keyCount = sumBy(dbNode.children, 'keyCount')
|
||||
const dbInst = this.databases[this.db]
|
||||
if (dbInst != null) {
|
||||
dbInst.keyCount = sumBy(dbNode.children, (c) => c.keyCount)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* add keys to current opened database
|
||||
* @param {ConnectionType} type
|
||||
* @param {string} keyPath
|
||||
* @return {RedisNodeItem|null}
|
||||
*/
|
||||
getNode(type, keyPath) {
|
||||
return this.nodeMap.get(`${type}/${keyPath}`) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* delete node and all it's children from nodeMap
|
||||
* @param {string} [key] clean nodeMap if key is empty
|
||||
* @private
|
||||
*/
|
||||
deleteChildrenKeyNodes(key) {
|
||||
if (isEmpty(key)) {
|
||||
this.nodeMap.clear()
|
||||
this.getRoot()
|
||||
} else {
|
||||
const nodeKey = `${ConnectionType.RedisKey}/${key}`
|
||||
const node = this.nodeMap.get(nodeKey)
|
||||
const children = node.children || []
|
||||
for (const child of children) {
|
||||
if (child.type === ConnectionType.RedisValue) {
|
||||
if (!this.nodeMap.delete(`${ConnectionType.RedisValue}/${child.redisKey}`)) {
|
||||
console.warn('delete:', `${ConnectionType.RedisValue}/${child.redisKey}`)
|
||||
}
|
||||
} else if (child.type === ConnectionType.RedisKey) {
|
||||
this.deleteChildrenKeyNodes(child.redisKey)
|
||||
}
|
||||
}
|
||||
if (!this.nodeMap.delete(nodeKey)) {
|
||||
console.warn('delete map key', nodeKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getFilter() {
|
||||
let pattern = this.patternFilter
|
||||
if (isEmpty(pattern)) {
|
||||
const conn = useConnectionStore()
|
||||
pattern = conn.getDefaultKeyFilter(this.name)
|
||||
}
|
||||
return {
|
||||
match: pattern,
|
||||
type: toUpper(this.typeFilter),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set key filter
|
||||
* @param {string} [pattern]
|
||||
* @param {string} [type]
|
||||
*/
|
||||
setFilter({ pattern, type }) {
|
||||
this.patternFilter = pattern === null ? this.patternFilter : pattern
|
||||
this.typeFilter = type === null ? this.typeFilter : type
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue