2023-11-05 11:57:52 +08:00
|
|
|
import { defineStore } from 'pinia'
|
2023-11-05 13:00:03 +08:00
|
|
|
import {
|
|
|
|
endsWith,
|
|
|
|
find,
|
|
|
|
get,
|
|
|
|
isEmpty,
|
|
|
|
join,
|
2023-11-09 00:30:07 +08:00
|
|
|
last,
|
2023-11-05 13:00:03 +08:00
|
|
|
remove,
|
|
|
|
set,
|
|
|
|
size,
|
|
|
|
slice,
|
|
|
|
sortedIndexBy,
|
|
|
|
split,
|
|
|
|
sumBy,
|
|
|
|
toUpper,
|
|
|
|
} from 'lodash'
|
2023-11-05 11:57:52 +08:00
|
|
|
import {
|
|
|
|
AddHashField,
|
|
|
|
AddListItem,
|
|
|
|
AddStreamValue,
|
|
|
|
AddZSetValue,
|
|
|
|
CleanCmdHistory,
|
|
|
|
CloseConnection,
|
2023-11-13 15:28:13 +08:00
|
|
|
ConvertValue,
|
2023-11-05 11:57:52 +08:00
|
|
|
DeleteKey,
|
|
|
|
FlushDB,
|
|
|
|
GetCmdHistory,
|
2023-11-08 23:45:33 +08:00
|
|
|
GetKeyDetail,
|
|
|
|
GetKeySummary,
|
2023-11-05 11:57:52 +08:00
|
|
|
GetSlowLogs,
|
|
|
|
LoadAllKeys,
|
|
|
|
LoadNextKeys,
|
|
|
|
OpenConnection,
|
|
|
|
OpenDatabase,
|
|
|
|
RemoveStreamValues,
|
|
|
|
RenameKey,
|
|
|
|
ServerInfo,
|
|
|
|
SetHashValue,
|
|
|
|
SetKeyTTL,
|
|
|
|
SetKeyValue,
|
|
|
|
SetListItem,
|
|
|
|
SetSetItem,
|
|
|
|
UpdateSetItem,
|
|
|
|
UpdateZSetValue,
|
|
|
|
} from 'wailsjs/go/services/browserService.js'
|
|
|
|
import useTabStore from 'stores/tab.js'
|
|
|
|
import { decodeRedisKey, nativeRedisKey } from '@/utils/key_convert.js'
|
|
|
|
import { BrowserTabType } from '@/consts/browser_tab_type.js'
|
|
|
|
import { KeyViewType } from '@/consts/key_view_type.js'
|
|
|
|
import { ConnectionType } from '@/consts/connection_type.js'
|
|
|
|
import { types } from '@/consts/support_redis_type.js'
|
|
|
|
import useConnectionStore from 'stores/connections.js'
|
2023-11-13 15:28:13 +08:00
|
|
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
2023-11-05 11:57:52 +08:00
|
|
|
|
|
|
|
const useBrowserStore = defineStore('browser', {
|
|
|
|
/**
|
|
|
|
* @typedef {Object} DatabaseItem
|
|
|
|
* @property {string} key - tree node unique 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 {string} [redisKey] - redis key, type == ConnectionType.RedisKey || type == ConnectionType.RedisValue only
|
2023-11-08 23:45:33 +08:00
|
|
|
* @property {number[]} [redisKeyCode] - redis key char code array, optional for redis key which contains binary data
|
2023-11-05 11:57:52 +08:00
|
|
|
* @property {number} [keys] - children key count
|
2023-11-05 13:00:03 +08:00
|
|
|
* @property {number} [maxKeys] - max key count for database
|
2023-11-05 11:57:52 +08:00
|
|
|
* @property {boolean} [isLeaf]
|
|
|
|
* @property {boolean} [opened] - redis db is opened, type == ConnectionType.RedisDB only
|
|
|
|
* @property {boolean} [expanded] - current node is expanded
|
|
|
|
* @property {DatabaseItem[]} [children]
|
|
|
|
* @property {boolean} [loading] - indicated that is loading children now
|
|
|
|
* @property {boolean} [fullLoaded] - indicated that all children already loaded
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef {Object} HistoryItem
|
|
|
|
* @property {string} time
|
|
|
|
* @property {string} server
|
|
|
|
* @property {string} cmd
|
|
|
|
* @property {number} cost
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef {Object} BrowserState
|
|
|
|
* @property {Object} serverStats
|
|
|
|
* @property {Object.<string, string>} keyFilter key is 'server#db', 'server#-1' stores default filter pattern
|
|
|
|
* @property {Object.<string, string>} typeFilter key is 'server#db'
|
|
|
|
* @property {Object.<string, KeyViewType>} viewType
|
|
|
|
* @property {Object.<string, DatabaseItem[]>} databases
|
|
|
|
* @property {Object.<string, Map<string, DatabaseItem>>} nodeMap key format likes 'server#db', children key format likes 'key#type'
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @returns {BrowserState}
|
|
|
|
*/
|
|
|
|
state: () => ({
|
|
|
|
serverStats: {}, // current server status info
|
|
|
|
keyFilter: {}, // all key filters in opened connections group by 'server+db'
|
|
|
|
typeFilter: {}, // all key type filters in opened connections group by 'server+db'
|
|
|
|
viewType: {}, // view type selection for all opened connections group by 'server'
|
|
|
|
databases: {}, // all databases in opened connections group by 'server name'
|
|
|
|
nodeMap: {}, // all nodes in opened connections group by 'server#db' and 'type/key'
|
|
|
|
keySet: {}, // all keys set in opened connections group by 'server#db
|
|
|
|
}),
|
|
|
|
getters: {
|
|
|
|
anyConnectionOpened() {
|
|
|
|
return !isEmpty(this.databases)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
actions: {
|
|
|
|
/**
|
|
|
|
* check if connection is connected
|
|
|
|
* @param name
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isConnected(name) {
|
|
|
|
let dbs = get(this.databases, name, [])
|
|
|
|
return !isEmpty(dbs)
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* close all connections
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async closeAllConnection() {
|
|
|
|
for (const name in this.databases) {
|
|
|
|
await CloseConnection(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.databases = {}
|
|
|
|
this.nodeMap.clear()
|
|
|
|
this.keySet.clear()
|
|
|
|
this.serverStats = {}
|
|
|
|
const tabStore = useTabStore()
|
|
|
|
tabStore.removeAllTab()
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get database by server name and index
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
2023-11-10 11:52:54 +08:00
|
|
|
* @return {DatabaseItem|null}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
|
|
|
getDatabase(connName, db) {
|
|
|
|
const dbs = this.databases[connName]
|
|
|
|
if (dbs != null) {
|
|
|
|
const selDB = find(dbs, (item) => item.db === db)
|
|
|
|
if (selDB != null) {
|
|
|
|
return selDB
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
},
|
|
|
|
|
2023-11-10 11:52:54 +08:00
|
|
|
/**
|
|
|
|
* get full loaded status of database
|
|
|
|
* @param connName
|
|
|
|
* @param db
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
isFullLoaded(connName, db) {
|
|
|
|
const selDB = this.getDatabase(connName, db)
|
|
|
|
if (selDB != null) {
|
|
|
|
return selDB.fullLoaded === true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
|
2023-11-05 11:57:52 +08:00
|
|
|
/**
|
|
|
|
* switch key view
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} viewType
|
|
|
|
*/
|
|
|
|
async switchKeyView(connName, viewType) {
|
|
|
|
if (viewType !== KeyViewType.Tree && viewType !== KeyViewType.List) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const t = get(this.viewType, connName, KeyViewType.Tree)
|
|
|
|
if (t === viewType) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.viewType[connName] = viewType
|
|
|
|
const dbs = get(this.databases, connName, [])
|
|
|
|
for (const dbItem of dbs) {
|
|
|
|
if (!dbItem.opened) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
dbItem.children = undefined
|
|
|
|
dbItem.keys = 0
|
|
|
|
const { db = 0 } = dbItem
|
|
|
|
this._getNodeMap(connName, db).clear()
|
|
|
|
const keys = this._getKeySet(connName, db)
|
|
|
|
this._addKeyNodes(connName, db, keys)
|
|
|
|
this._tidyNode(connName, db, '')
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* open connection
|
|
|
|
* @param {string} name
|
|
|
|
* @param {boolean} [reload]
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async openConnection(name, reload) {
|
|
|
|
if (this.isConnected(name)) {
|
|
|
|
if (reload !== true) {
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
// reload mode, try close connection first
|
|
|
|
await CloseConnection(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const { data, success, msg } = await OpenConnection(name)
|
|
|
|
if (!success) {
|
|
|
|
throw new Error(msg)
|
|
|
|
}
|
|
|
|
// append to db node to current connection
|
|
|
|
// const connNode = this.getConnection(name)
|
|
|
|
// if (connNode == null) {
|
|
|
|
// throw new Error('no such connection')
|
|
|
|
// }
|
|
|
|
const { db, view = KeyViewType.Tree } = data
|
|
|
|
if (isEmpty(db)) {
|
|
|
|
throw new Error('no db loaded')
|
|
|
|
}
|
|
|
|
const dbs = []
|
|
|
|
for (let i = 0; i < db.length; i++) {
|
|
|
|
this._getNodeMap(name, i).clear()
|
|
|
|
this._getKeySet(name, i).clear()
|
|
|
|
dbs.push({
|
|
|
|
key: `${name}/${db[i].name}`,
|
|
|
|
label: db[i].name,
|
|
|
|
name: name,
|
|
|
|
keys: 0,
|
|
|
|
maxKeys: db[i].keys,
|
|
|
|
db: db[i].index,
|
|
|
|
type: ConnectionType.RedisDB,
|
|
|
|
isLeaf: false,
|
|
|
|
children: undefined,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
this.databases[name] = dbs
|
|
|
|
this.viewType[name] = view
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* close connection
|
|
|
|
* @param {string} name
|
|
|
|
* @returns {Promise<boolean>}
|
|
|
|
*/
|
|
|
|
async closeConnection(name) {
|
|
|
|
const { success, msg } = await CloseConnection(name)
|
|
|
|
if (!success) {
|
|
|
|
// throw new Error(msg)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
const dbs = this.databases[name]
|
|
|
|
if (!isEmpty(dbs)) {
|
|
|
|
for (const db of dbs) {
|
|
|
|
this.removeKeyFilter(name, db.db)
|
|
|
|
this._getNodeMap(name, db.db).clear()
|
|
|
|
this._getKeySet(name, db.db).clear()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.removeKeyFilter(name, -1)
|
|
|
|
delete this.databases[name]
|
|
|
|
delete this.serverStats[name]
|
|
|
|
|
|
|
|
const tabStore = useTabStore()
|
|
|
|
tabStore.removeTabByName(name)
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* open database and load all keys
|
|
|
|
* @param connName
|
|
|
|
* @param db
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async openDatabase(connName, db) {
|
|
|
|
const { match: filterPattern, type: filterType } = this.getKeyFilter(connName, db)
|
|
|
|
const { data, success, msg } = await OpenDatabase(connName, db, filterPattern, filterType)
|
|
|
|
if (!success) {
|
|
|
|
throw new Error(msg)
|
|
|
|
}
|
2023-11-05 13:00:03 +08:00
|
|
|
const { keys = [], end = false, maxKeys = 0 } = data
|
2023-11-05 11:57:52 +08:00
|
|
|
const selDB = this.getDatabase(connName, db)
|
|
|
|
if (selDB == null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
selDB.opened = true
|
|
|
|
selDB.fullLoaded = end
|
2023-11-05 13:00:03 +08:00
|
|
|
selDB.maxKeys = maxKeys
|
2023-11-05 11:57:52 +08:00
|
|
|
if (isEmpty(keys)) {
|
|
|
|
selDB.children = []
|
|
|
|
} else {
|
|
|
|
// append db node to current connection's children
|
|
|
|
this._addKeyNodes(connName, db, keys)
|
|
|
|
}
|
|
|
|
this._tidyNode(connName, db)
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* reopen database
|
|
|
|
* @param connName
|
|
|
|
* @param db
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async reopenDatabase(connName, db) {
|
|
|
|
const selDB = this.getDatabase(connName, db)
|
|
|
|
if (selDB == null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
selDB.children = undefined
|
|
|
|
selDB.isLeaf = false
|
|
|
|
|
|
|
|
this._getNodeMap(connName, db).clear()
|
|
|
|
this._getKeySet(connName, db).clear()
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* close database
|
|
|
|
* @param connName
|
|
|
|
* @param db
|
|
|
|
*/
|
|
|
|
closeDatabase(connName, db) {
|
|
|
|
const selDB = this.getDatabase(connName, db)
|
|
|
|
if (selDB == null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
delete selDB.children
|
|
|
|
selDB.isLeaf = false
|
|
|
|
selDB.opened = false
|
|
|
|
|
|
|
|
this._getNodeMap(connName, db).clear()
|
|
|
|
this._getKeySet(connName, db).clear()
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param server
|
|
|
|
* @returns {Promise<{}>}
|
|
|
|
*/
|
|
|
|
async getServerInfo(server) {
|
|
|
|
try {
|
|
|
|
const { success, data } = await ServerInfo(server)
|
|
|
|
if (success) {
|
|
|
|
this.serverStats[server] = data
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
}
|
|
|
|
return {}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2023-11-08 23:45:33 +08:00
|
|
|
* load key summary info
|
2023-11-05 11:57:52 +08:00
|
|
|
* @param {string} server
|
|
|
|
* @param {number} db
|
2023-11-08 23:45:33 +08:00
|
|
|
* @param {string|number[]} [key] null or blank indicate that update tab to display normal content (blank content or server status)
|
|
|
|
* @return {Promise<void>}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
2023-11-08 23:45:33 +08:00
|
|
|
async loadKeySummary({ server, db, key }) {
|
2023-11-05 11:57:52 +08:00
|
|
|
try {
|
|
|
|
const tab = useTabStore()
|
|
|
|
if (!isEmpty(key)) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const { data, success, msg } = await GetKeySummary({
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
key,
|
|
|
|
})
|
2023-11-05 11:57:52 +08:00
|
|
|
if (success) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const { type, ttl, size, length } = data
|
2023-11-05 11:57:52 +08:00
|
|
|
const k = decodeRedisKey(key)
|
|
|
|
const binaryKey = k !== key
|
|
|
|
tab.upsertTab({
|
|
|
|
subTab: BrowserTabType.KeyDetail,
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
type,
|
|
|
|
ttl,
|
|
|
|
keyCode: binaryKey ? key : undefined,
|
|
|
|
key: k,
|
|
|
|
size,
|
|
|
|
length,
|
|
|
|
})
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
if (!isEmpty(msg)) {
|
2023-11-08 23:45:33 +08:00
|
|
|
$message.error('load key summary fail: ' + msg)
|
2023-11-05 11:57:52 +08:00
|
|
|
}
|
|
|
|
// its danger to delete "non-exists" key, just remove from tree view
|
|
|
|
await this.deleteKey(server, db, key, true)
|
|
|
|
// TODO: show key not found page or check exists on server first?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tab.upsertTab({
|
|
|
|
subTab: BrowserTabType.Status,
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
type: 'none',
|
|
|
|
ttl: -1,
|
|
|
|
key: null,
|
|
|
|
keyCode: null,
|
|
|
|
value: null,
|
|
|
|
size: 0,
|
|
|
|
length: 0,
|
|
|
|
})
|
2023-11-08 23:45:33 +08:00
|
|
|
} catch (e) {
|
|
|
|
$message.error('')
|
|
|
|
} finally {
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* reload key
|
2023-11-13 22:31:18 +08:00
|
|
|
* @param {string} server
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {string} [decode]
|
|
|
|
* @param {string} [format]
|
2023-11-08 23:45:33 +08:00
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
2023-11-13 22:31:18 +08:00
|
|
|
async reloadKey({ server, db, key, decode, format }) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
try {
|
|
|
|
tab.updateLoading({ server, db, loading: true })
|
|
|
|
await this.loadKeySummary({ server, db, key })
|
2023-11-13 22:31:18 +08:00
|
|
|
await this.loadKeyDetail({ server, db, key, decode, format, reset: true })
|
2023-11-08 23:45:33 +08:00
|
|
|
} finally {
|
|
|
|
tab.updateLoading({ server, db, loading: false })
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* load key content
|
|
|
|
* @param {string} server
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
2023-11-13 22:31:18 +08:00
|
|
|
* @param {string} [format]
|
|
|
|
* @param {string} [decode]
|
2023-11-08 23:45:33 +08:00
|
|
|
* @param {string} [matchPattern]
|
|
|
|
* @param {boolean} [reset]
|
|
|
|
* @param {boolean} [full]
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
2023-11-13 22:31:18 +08:00
|
|
|
async loadKeyDetail({ server, db, key, format, decode, matchPattern, reset, full }) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
try {
|
|
|
|
tab.updateLoading({ server, db, loading: true })
|
|
|
|
const { data, success, msg } = await GetKeyDetail({
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
key,
|
2023-11-13 22:31:18 +08:00
|
|
|
format,
|
|
|
|
decode,
|
2023-11-08 23:45:33 +08:00
|
|
|
matchPattern,
|
|
|
|
full: full === true,
|
|
|
|
reset,
|
|
|
|
lite: true,
|
|
|
|
})
|
|
|
|
if (success) {
|
2023-11-13 22:31:18 +08:00
|
|
|
const { value, decode: retDecode, format: retFormat, end } = data
|
2023-11-08 23:45:33 +08:00
|
|
|
tab.updateValue({
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
key: decodeRedisKey(key),
|
|
|
|
value,
|
2023-11-13 22:31:18 +08:00
|
|
|
decode: retDecode,
|
|
|
|
format: retFormat,
|
2023-11-08 23:45:33 +08:00
|
|
|
reset: reset || full === true,
|
|
|
|
end,
|
|
|
|
})
|
2023-11-13 22:31:18 +08:00
|
|
|
} else {
|
|
|
|
$message.error('load key detail fail:' + msg)
|
2023-11-08 23:45:33 +08:00
|
|
|
}
|
2023-11-05 11:57:52 +08:00
|
|
|
} finally {
|
2023-11-08 23:45:33 +08:00
|
|
|
tab.updateLoading({ server, db, loading: false })
|
2023-11-05 11:57:52 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-11-13 15:28:13 +08:00
|
|
|
/**
|
|
|
|
* convert value by decode type or format
|
|
|
|
* @param {string|number[]} value
|
|
|
|
* @param {string} [decode]
|
|
|
|
* @param {string} [format]
|
|
|
|
* @return {Promise<{[format]: string, [decode]: string, value: string}>}
|
|
|
|
*/
|
|
|
|
async convertValue({ value, decode, format }) {
|
|
|
|
try {
|
|
|
|
const { data, success } = await ConvertValue(value, decode, format)
|
|
|
|
if (success) {
|
|
|
|
const { value: retVal, decode: retDecode, format: retFormat } = data
|
|
|
|
return { value: retVal, decode: retDecode, format: retFormat }
|
|
|
|
}
|
|
|
|
} catch (e) {}
|
|
|
|
return { value, decode, format }
|
|
|
|
},
|
|
|
|
|
2023-11-05 11:57:52 +08:00
|
|
|
/**
|
|
|
|
* scan keys with prefix
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} match
|
2023-11-10 11:52:54 +08:00
|
|
|
* @param {string} [matchType]
|
2023-11-05 11:57:52 +08:00
|
|
|
* @param {boolean} [full]
|
2023-11-10 11:52:54 +08:00
|
|
|
* @returns {Promise<{keys: string[], maxKeys: number, end: boolean}>}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
|
|
|
async scanKeys(connName, db, match, matchType, full) {
|
|
|
|
let resp
|
|
|
|
if (full) {
|
|
|
|
resp = await LoadAllKeys(connName, db, match || '*', matchType)
|
|
|
|
} else {
|
|
|
|
resp = await LoadNextKeys(connName, db, match || '*', matchType)
|
|
|
|
}
|
|
|
|
const { data, success, msg } = resp || {}
|
|
|
|
if (!success) {
|
|
|
|
throw new Error(msg)
|
|
|
|
}
|
2023-11-10 11:52:54 +08:00
|
|
|
const { keys = [], maxKeys, end } = data
|
|
|
|
return { keys, end, maxKeys, success }
|
2023-11-05 11:57:52 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string|null} prefix
|
|
|
|
* @param {string|null} matchType
|
|
|
|
* @param {boolean} [all]
|
2023-11-10 11:52:54 +08:00
|
|
|
* @return {Promise<{keys: Array<string|number[]>, maxKeys: number, end: boolean}>}
|
2023-11-05 11:57:52 +08:00
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
async _loadKeys(connName, db, prefix, matchType, all) {
|
|
|
|
let match = prefix
|
|
|
|
if (isEmpty(match)) {
|
|
|
|
match = '*'
|
|
|
|
} else {
|
|
|
|
const separator = this._getSeparator(connName)
|
|
|
|
if (!endsWith(prefix, separator + '*')) {
|
|
|
|
match = prefix + separator + '*'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.scanKeys(connName, db, match, matchType, all)
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* load more keys within the database
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @return {Promise<boolean>}
|
|
|
|
*/
|
|
|
|
async loadMoreKeys(connName, db) {
|
|
|
|
const { match, type: keyType } = this.getKeyFilter(connName, db)
|
2023-11-10 11:52:54 +08:00
|
|
|
const { keys, maxKeys, end } = await this._loadKeys(connName, db, match, keyType, false)
|
|
|
|
this._setDBMaxKeys(connName, db, maxKeys)
|
2023-11-05 11:57:52 +08:00
|
|
|
// remove current keys below prefix
|
|
|
|
this._addKeyNodes(connName, db, keys)
|
|
|
|
this._tidyNode(connName, db, '')
|
|
|
|
return end
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* load all left keys within the database
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
async loadAllKeys(connName, db) {
|
|
|
|
const { match, type: keyType } = this.getKeyFilter(connName, db)
|
2023-11-10 11:52:54 +08:00
|
|
|
const { keys, maxKeys } = await this._loadKeys(connName, db, match, keyType, true)
|
|
|
|
this._setDBMaxKeys(connName, db, maxKeys)
|
2023-11-05 11:57:52 +08:00
|
|
|
this._addKeyNodes(connName, db, keys)
|
|
|
|
this._tidyNode(connName, db, '')
|
|
|
|
},
|
|
|
|
|
2023-11-10 11:52:54 +08:00
|
|
|
/**
|
|
|
|
* reload keys under layer
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} prefix
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
|
|
|
async reloadLayer(connName, db, prefix) {
|
|
|
|
if (isEmpty(prefix)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let match = prefix
|
|
|
|
const separator = this._getSeparator(connName)
|
|
|
|
if (!endsWith(match, separator)) {
|
|
|
|
match += separator + '*'
|
|
|
|
} else {
|
|
|
|
match += '*'
|
|
|
|
}
|
|
|
|
// FIXME: ignore original match pattern due to redis not support combination matching
|
|
|
|
const { match: originMatch, type: keyType } = this.getKeyFilter(connName, db)
|
|
|
|
const { keys, maxKeys, success } = await this._loadKeys(connName, db, match, keyType, true)
|
|
|
|
if (!success) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this._setDBMaxKeys(connName, db, maxKeys)
|
|
|
|
// remove current keys below prefix
|
|
|
|
this._deleteKeyNode(connName, db, prefix, true)
|
|
|
|
this._addKeyNodes(connName, db, keys)
|
|
|
|
this._tidyNode(connName, db, prefix)
|
|
|
|
},
|
|
|
|
|
2023-11-05 11:57:52 +08:00
|
|
|
/**
|
|
|
|
* get custom separator of connection
|
|
|
|
* @param server
|
|
|
|
* @returns {string}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_getSeparator(server) {
|
|
|
|
const connStore = useConnectionStore()
|
|
|
|
const { keySeparator } = connStore.getDefaultSeparator(server)
|
|
|
|
if (isEmpty(keySeparator)) {
|
|
|
|
return ':'
|
|
|
|
}
|
|
|
|
return keySeparator
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get node map
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @returns {Map<string, DatabaseItem>}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_getNodeMap(connName, db) {
|
|
|
|
if (!this.nodeMap.hasOwnProperty(`${connName}#${db}`)) {
|
|
|
|
this.nodeMap[`${connName}#${db}`] = new Map()
|
|
|
|
}
|
|
|
|
// construct a tree node list, the format of item key likes 'server/db#type/key'
|
|
|
|
return this.nodeMap[`${connName}#${db}`]
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get all keys in a database
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @return {Set<string|number[]>}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_getKeySet(connName, db) {
|
|
|
|
if (!this.keySet.hasOwnProperty(`${connName}#${db}`)) {
|
|
|
|
this.keySet[`${connName}#${db}`] = new Set()
|
|
|
|
}
|
|
|
|
// construct a key set
|
|
|
|
return this.keySet[`${connName}#${db}`]
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* remove keys in db
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {Array<string|number[]>|Set<string|number[]>} keys
|
|
|
|
* @param {boolean} [sortInsert]
|
|
|
|
* @return {{success: boolean, newKey: number, newLayer: number, replaceKey: number}}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_addKeyNodes(connName, db, keys, sortInsert) {
|
|
|
|
const result = {
|
|
|
|
success: false,
|
|
|
|
newLayer: 0,
|
|
|
|
newKey: 0,
|
|
|
|
replaceKey: 0,
|
|
|
|
}
|
|
|
|
if (isEmpty(keys)) {
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
const separator = this._getSeparator(connName)
|
|
|
|
const selDB = this.getDatabase(connName, db)
|
|
|
|
if (selDB == null) {
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selDB.children == null) {
|
|
|
|
selDB.children = []
|
|
|
|
}
|
|
|
|
const nodeMap = this._getNodeMap(connName, db)
|
|
|
|
const keySet = this._getKeySet(connName, db)
|
|
|
|
const rootChildren = selDB.children
|
|
|
|
const viewType = get(this.viewType, connName, KeyViewType.Tree)
|
|
|
|
if (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 = nodeMap.has(nodeKey)
|
|
|
|
const selectedNode = {
|
|
|
|
key: `${connName}/db${db}#${nodeKey}`,
|
|
|
|
label: k,
|
|
|
|
db,
|
|
|
|
keys: 0,
|
|
|
|
redisKey: k,
|
|
|
|
redisKeyCode: isBinaryKey ? key : undefined,
|
|
|
|
type: ConnectionType.RedisValue,
|
|
|
|
isLeaf: true,
|
|
|
|
}
|
|
|
|
nodeMap.set(nodeKey, selectedNode)
|
|
|
|
keySet.add(key)
|
|
|
|
if (!replaceKey) {
|
|
|
|
if (sortInsert) {
|
|
|
|
const index = sortedIndexBy(rootChildren, selectedNode, 'key')
|
|
|
|
rootChildren.splice(index, 0, selectedNode)
|
|
|
|
} else {
|
|
|
|
rootChildren.push(selectedNode)
|
|
|
|
}
|
|
|
|
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, separator)
|
|
|
|
const len = size(keyParts)
|
|
|
|
const lastIdx = len - 1
|
|
|
|
let handlePath = ''
|
|
|
|
let children = rootChildren
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
handlePath += keyParts[i]
|
|
|
|
if (i !== lastIdx) {
|
|
|
|
// layer
|
|
|
|
const nodeKey = `${ConnectionType.RedisKey}/${handlePath}`
|
|
|
|
let selectedNode = nodeMap.get(nodeKey)
|
|
|
|
if (selectedNode == null) {
|
|
|
|
selectedNode = {
|
|
|
|
key: `${connName}/db${db}#${nodeKey}`,
|
|
|
|
label: keyParts[i],
|
|
|
|
db,
|
|
|
|
keys: 0,
|
|
|
|
redisKey: handlePath,
|
|
|
|
type: ConnectionType.RedisKey,
|
|
|
|
isLeaf: false,
|
|
|
|
children: [],
|
|
|
|
}
|
|
|
|
nodeMap.set(nodeKey, selectedNode)
|
|
|
|
if (sortInsert) {
|
|
|
|
const index = sortedIndexBy(children, selectedNode, 'key')
|
|
|
|
children.splice(index, 0, selectedNode)
|
|
|
|
} else {
|
|
|
|
children.push(selectedNode)
|
|
|
|
}
|
|
|
|
result.newLayer += 1
|
|
|
|
}
|
|
|
|
children = selectedNode.children
|
|
|
|
handlePath += separator
|
|
|
|
} else {
|
|
|
|
// key
|
|
|
|
const nodeKey = `${ConnectionType.RedisValue}/${handlePath}`
|
|
|
|
const replaceKey = nodeMap.has(nodeKey)
|
|
|
|
const selectedNode = {
|
|
|
|
key: `${connName}/db${db}#${nodeKey}`,
|
|
|
|
label: isBinaryKey ? k : keyParts[i],
|
|
|
|
db,
|
|
|
|
keys: 0,
|
|
|
|
redisKey: handlePath,
|
|
|
|
redisKeyCode: isBinaryKey ? key : undefined,
|
|
|
|
type: ConnectionType.RedisValue,
|
|
|
|
isLeaf: true,
|
|
|
|
}
|
|
|
|
nodeMap.set(nodeKey, selectedNode)
|
|
|
|
keySet.add(key)
|
|
|
|
if (!replaceKey) {
|
|
|
|
if (sortInsert) {
|
|
|
|
const index = sortedIndexBy(children, selectedNode, 'key')
|
|
|
|
children.splice(index, 0, selectedNode)
|
|
|
|
} else {
|
|
|
|
children.push(selectedNode)
|
|
|
|
}
|
|
|
|
result.newKey += 1
|
|
|
|
} else {
|
|
|
|
result.replaceKey += 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {DatabaseItem[]} nodeList
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_sortNodes(nodeList) {
|
|
|
|
if (nodeList == null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
nodeList.sort((a, b) => {
|
|
|
|
return a.key > b.key ? 1 : -1
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* tidy node by key
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} [key]
|
|
|
|
* @param {boolean} [skipResort]
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_tidyNode(connName, db, key, skipResort) {
|
|
|
|
const nodeMap = this._getNodeMap(connName, db)
|
|
|
|
const dbNode = this.getDatabase(connName, db) || {}
|
|
|
|
const separator = this._getSeparator(connName)
|
|
|
|
const keyParts = split(key, 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), separator)
|
|
|
|
node = nodeMap.get(`${ConnectionType.RedisKey}/${parentKey}`)
|
|
|
|
if (node != null) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (node == null) {
|
|
|
|
node = dbNode
|
|
|
|
}
|
|
|
|
const keyCountUpdated = this._tidyNodeChildren(node, skipResort)
|
|
|
|
|
|
|
|
if (keyCountUpdated) {
|
|
|
|
// update key count of parent and above
|
|
|
|
for (; i > 0; i--) {
|
|
|
|
const parentKey = join(slice(keyParts, 0, i), separator)
|
|
|
|
const parentNode = nodeMap.get(`${ConnectionType.RedisKey}/${parentKey}`)
|
|
|
|
if (parentNode == null) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
parentNode.keys = sumBy(parentNode.children, 'keys')
|
|
|
|
}
|
|
|
|
// update key count of db
|
|
|
|
dbNode.keys = sumBy(dbNode.children, 'keys')
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* sort all node item's children and calculate keys count
|
|
|
|
* @param {DatabaseItem} node
|
|
|
|
* @param {boolean} skipSort skip sorting children
|
|
|
|
* @returns {boolean} return whether key count changed
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_tidyNodeChildren(node, skipSort) {
|
|
|
|
let count = 0
|
|
|
|
if (!isEmpty(node.children)) {
|
|
|
|
if (skipSort !== true) {
|
|
|
|
this._sortNodes(node.children)
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const elem of node.children) {
|
|
|
|
this._tidyNodeChildren(elem, skipSort)
|
|
|
|
count += elem.keys
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (node.type === ConnectionType.RedisValue) {
|
|
|
|
count += 1
|
|
|
|
} else {
|
|
|
|
// no children in db node or layer node, set count to 0
|
|
|
|
count = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (node.keys !== count) {
|
|
|
|
node.keys = count
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
|
2023-11-05 13:00:03 +08:00
|
|
|
/**
|
2023-11-10 11:52:54 +08:00
|
|
|
* update max key by increase/decrease value
|
2023-11-05 13:00:03 +08:00
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
2023-11-10 11:52:54 +08:00
|
|
|
* @param {number} [updateValue]
|
2023-11-05 13:00:03 +08:00
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_updateDBMaxKeys(connName, db, updateValue) {
|
2023-11-10 11:52:54 +08:00
|
|
|
if (updateValue === undefined) {
|
|
|
|
return
|
|
|
|
}
|
2023-11-05 13:00:03 +08:00
|
|
|
const database = this.getDatabase(connName, db)
|
|
|
|
if (database != null) {
|
|
|
|
const maxKeys = get(database, 'maxKeys', 0)
|
|
|
|
database.maxKeys = Math.max(0, maxKeys + updateValue)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2023-11-10 11:52:54 +08:00
|
|
|
* set db max keys value
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {number} maxKeys
|
2023-11-05 13:00:03 +08:00
|
|
|
* @private
|
|
|
|
*/
|
2023-11-10 11:52:54 +08:00
|
|
|
_setDBMaxKeys(connName, db, maxKeys) {
|
2023-11-05 13:00:03 +08:00
|
|
|
const database = this.getDatabase(connName, db)
|
|
|
|
if (database != null) {
|
2023-11-10 11:52:54 +08:00
|
|
|
set(database, 'maxKeys', maxKeys)
|
2023-11-05 13:00:03 +08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-11-05 11:57:52 +08:00
|
|
|
/**
|
|
|
|
* get tree node by key name
|
|
|
|
* @param key
|
|
|
|
* @return {DatabaseItem|null}
|
|
|
|
*/
|
|
|
|
getNode(key) {
|
|
|
|
let idx = key.indexOf('#')
|
|
|
|
if (idx < 0) {
|
|
|
|
idx = size(key)
|
|
|
|
}
|
|
|
|
const dbPart = key.substring(0, idx)
|
|
|
|
// parse server and db index
|
|
|
|
const idx2 = dbPart.lastIndexOf('/db')
|
|
|
|
if (idx2 < 0) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
const server = dbPart.substring(0, idx2)
|
|
|
|
const db = parseInt(dbPart.substring(idx2 + 3))
|
|
|
|
if (isNaN(db)) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size(key) > idx + 1) {
|
|
|
|
const keyPart = key.substring(idx + 1)
|
|
|
|
// contains redis key
|
|
|
|
const nodeMap = this._getNodeMap(server, db)
|
|
|
|
return nodeMap.get(keyPart)
|
|
|
|
} else {
|
|
|
|
return this.getDatabase(server, db)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* set redis key
|
2023-11-13 15:28:13 +08:00
|
|
|
* @param {string} server
|
2023-11-05 11:57:52 +08:00
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {string} keyType
|
|
|
|
* @param {any} value
|
|
|
|
* @param {number} ttl
|
2023-11-13 15:28:13 +08:00
|
|
|
* @param {string} [format]
|
2023-11-05 11:57:52 +08:00
|
|
|
* @param {string} [decode]
|
|
|
|
* @returns {Promise<{[msg]: string, success: boolean, [nodeKey]: {string}}>}
|
|
|
|
*/
|
2023-11-13 22:41:33 +08:00
|
|
|
async setKey({ server, db, key, keyType, value, ttl, format = formatTypes.RAW, decode = decodeTypes.NONE }) {
|
2023-11-05 11:57:52 +08:00
|
|
|
try {
|
2023-11-13 15:28:13 +08:00
|
|
|
const { data, success, msg } = await SetKeyValue({
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
key,
|
|
|
|
keyType,
|
|
|
|
value,
|
|
|
|
ttl,
|
|
|
|
format,
|
|
|
|
decode,
|
|
|
|
})
|
2023-11-05 11:57:52 +08:00
|
|
|
if (success) {
|
2023-11-13 15:28:13 +08:00
|
|
|
const { value } = data
|
2023-11-05 11:57:52 +08:00
|
|
|
// update tree view data
|
2023-11-13 15:28:13 +08:00
|
|
|
const { newKey = 0 } = this._addKeyNodes(server, db, [key], true)
|
2023-11-05 11:57:52 +08:00
|
|
|
if (newKey > 0) {
|
2023-11-13 15:28:13 +08:00
|
|
|
this._tidyNode(server, db, key)
|
|
|
|
this._updateDBMaxKeys(server, db, newKey)
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
success,
|
|
|
|
nodeKey: `${server}/db${db}#${ConnectionType.RedisValue}/${key}`,
|
|
|
|
updatedValue: value,
|
2023-11-05 11:57:52 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return { success, msg }
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return { success: false, msg: e.message }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2023-11-13 15:28:13 +08:00
|
|
|
* update hash entry
|
2023-11-05 11:57:52 +08:00
|
|
|
* 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
|
2023-11-13 15:28:13 +08:00
|
|
|
* @param {string} server
|
2023-11-05 11:57:52 +08:00
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {string} field
|
2023-11-13 15:28:13 +08:00
|
|
|
* @param {string} [newField]
|
|
|
|
* @param {string} [value]
|
|
|
|
* @param {string} [decode]
|
|
|
|
* @param {string} [format]
|
|
|
|
* @param {boolean} [refresh]
|
2023-11-05 11:57:52 +08:00
|
|
|
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
|
|
|
*/
|
2023-11-13 15:28:13 +08:00
|
|
|
async setHash({
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
key,
|
|
|
|
field,
|
|
|
|
newField = '',
|
|
|
|
value = '',
|
|
|
|
decode = decodeTypes.NONE,
|
2023-11-13 22:41:33 +08:00
|
|
|
format = formatTypes.RAW,
|
2023-11-13 15:28:13 +08:00
|
|
|
refresh,
|
|
|
|
}) {
|
2023-11-05 11:57:52 +08:00
|
|
|
try {
|
2023-11-13 15:28:13 +08:00
|
|
|
const { data, success, msg } = await SetHashValue({
|
|
|
|
server,
|
|
|
|
db,
|
|
|
|
key,
|
|
|
|
field,
|
|
|
|
newField,
|
|
|
|
value,
|
|
|
|
decode,
|
|
|
|
format,
|
|
|
|
})
|
2023-11-05 11:57:52 +08:00
|
|
|
if (success) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const { updated = {}, removed = [], replaced = {} } = data
|
2023-11-13 15:28:13 +08:00
|
|
|
if (refresh === true) {
|
|
|
|
const tab = useTabStore()
|
|
|
|
if (!isEmpty(removed)) {
|
|
|
|
tab.removeValueEntries({ server, db, key, type: 'hash', entries: removed })
|
|
|
|
}
|
|
|
|
if (!isEmpty(updated)) {
|
|
|
|
tab.upsertValueEntries({ server, db, key, type: 'hash', entries: updated })
|
|
|
|
}
|
2023-11-08 23:45:33 +08:00
|
|
|
}
|
2023-11-05 11:57:52 +08:00
|
|
|
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|number[]} 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
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
tab.upsertValueEntries({ server: connName, db, key, type: 'hash', entries: updated })
|
2023-11-05 11:57:52 +08:00
|
|
|
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
|
2023-11-08 23:45:33 +08:00
|
|
|
if (!isEmpty(removed)) {
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.removeValueEntries({ server: connName, db, key, type: 'hash', entries: removed })
|
|
|
|
}
|
2023-11-05 11:57:52 +08:00
|
|
|
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|number[]} 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
|
2023-11-08 23:45:33 +08:00
|
|
|
* @returns {Promise<{[msg]: string, success: boolean, [item]: []}>}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
|
|
|
async prependListItem(connName, db, key, values) {
|
|
|
|
try {
|
|
|
|
const { data, success, msg } = await AddListItem(connName, db, key, 0, values)
|
|
|
|
if (success) {
|
|
|
|
const { left = [] } = data
|
2023-11-08 23:45:33 +08:00
|
|
|
if (!isEmpty(left)) {
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.upsertValueEntries({
|
|
|
|
server: connName,
|
|
|
|
db,
|
|
|
|
key,
|
|
|
|
type: 'list',
|
|
|
|
entries: right,
|
|
|
|
prepend: true,
|
|
|
|
})
|
|
|
|
}
|
2023-11-05 11:57:52 +08:00
|
|
|
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
|
2023-11-08 23:45:33 +08:00
|
|
|
* @returns {Promise<{[msg]: string, success: boolean, [item]: any[]}>}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
|
|
|
async appendListItem(connName, db, key, values) {
|
|
|
|
try {
|
|
|
|
const { data, success, msg } = await AddListItem(connName, db, key, 1, values)
|
|
|
|
if (success) {
|
|
|
|
const { right = [] } = data
|
2023-11-08 23:45:33 +08:00
|
|
|
if (!isEmpty(right)) {
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.upsertValueEntries({
|
|
|
|
server: connName,
|
|
|
|
db,
|
|
|
|
key,
|
|
|
|
type: 'list',
|
|
|
|
entries: right,
|
|
|
|
prepend: false,
|
|
|
|
})
|
|
|
|
}
|
2023-11-05 11:57:52 +08:00
|
|
|
return { success, item: right }
|
|
|
|
} else {
|
|
|
|
return { success: false, msg }
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return { success: false, msg: e.message }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* update value of list item by index
|
2023-11-14 14:49:16 +08:00
|
|
|
* @param {string} server
|
2023-11-05 11:57:52 +08:00
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {number} index
|
2023-11-14 14:49:16 +08:00
|
|
|
* @param {string|number[]} value
|
|
|
|
* @param {string} decode
|
|
|
|
* @param {string} format
|
2023-11-05 11:57:52 +08:00
|
|
|
* @returns {Promise<{[msg]: string, success: boolean, [updated]: {}}>}
|
|
|
|
*/
|
2023-11-14 14:49:16 +08:00
|
|
|
async updateListItem({ server, db, key, index, value, decode, format }) {
|
2023-11-05 11:57:52 +08:00
|
|
|
try {
|
2023-11-14 14:49:16 +08:00
|
|
|
const { data, success, msg } = await SetListItem({ server, db, key, index, value, decode, format })
|
2023-11-05 11:57:52 +08:00
|
|
|
if (success) {
|
|
|
|
const { updated = {} } = data
|
2023-11-14 14:49:16 +08:00
|
|
|
// if (!isEmpty(updated)) {
|
|
|
|
// const tab = useTabStore()
|
|
|
|
// tab.upsertValueEntries({
|
|
|
|
// server,
|
|
|
|
// db,
|
|
|
|
// key,
|
|
|
|
// type: 'list',
|
|
|
|
// entries: updated,
|
|
|
|
// })
|
|
|
|
// }
|
2023-11-05 11:57:52 +08:00
|
|
|
return { success, updated }
|
|
|
|
} else {
|
|
|
|
return { success, msg }
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return { success: false, msg: e.message }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* remove list item
|
2023-11-14 14:49:16 +08:00
|
|
|
* @param {string} server
|
2023-11-05 11:57:52 +08:00
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {number} index
|
|
|
|
* @returns {Promise<{[msg]: string, success: boolean, [removed]: string[]}>}
|
|
|
|
*/
|
2023-11-14 14:49:16 +08:00
|
|
|
async removeListItem(server, db, key, index) {
|
2023-11-05 11:57:52 +08:00
|
|
|
try {
|
2023-11-14 14:49:16 +08:00
|
|
|
const { data, success, msg } = await SetListItem({ server, db, key, index })
|
2023-11-05 11:57:52 +08:00
|
|
|
if (success) {
|
|
|
|
const { removed = [] } = data
|
2023-11-08 23:45:33 +08:00
|
|
|
if (!isEmpty(removed)) {
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.removeValueEntries({
|
2023-11-14 14:49:16 +08:00
|
|
|
server,
|
2023-11-08 23:45:33 +08:00
|
|
|
db,
|
|
|
|
key,
|
|
|
|
type: 'list',
|
|
|
|
entries: removed,
|
|
|
|
})
|
|
|
|
}
|
2023-11-05 11:57:52 +08:00
|
|
|
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|number} key
|
2023-11-08 23:45:33 +08:00
|
|
|
* @param {string|string[]} value
|
2023-11-05 11:57:52 +08:00
|
|
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
|
|
|
*/
|
|
|
|
async addSetItem(connName, db, key, value) {
|
|
|
|
try {
|
2023-11-08 23:45:33 +08:00
|
|
|
if (!value instanceof Array) {
|
|
|
|
value = [value]
|
|
|
|
}
|
|
|
|
const { data, success, msg } = await SetSetItem(connName, db, key, false, value)
|
2023-11-05 11:57:52 +08:00
|
|
|
if (success) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
tab.upsertValueEntries({ server: connName, db, key, type: 'set', entries: value })
|
2023-11-05 11:57:52 +08:00
|
|
|
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|number[]} 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) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
tab.upsertValueEntries({ server: connName, db, key, type: 'set', entries: { [value]: newValue } })
|
2023-11-05 11:57:52 +08:00
|
|
|
return { success: true }
|
|
|
|
} else {
|
|
|
|
return { success, msg }
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return { success: false, msg: e.message }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* remove item from set
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {string} 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) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
tab.removeValueEntries({ server: connName, db, key, type: 'set', entries: [value] })
|
2023-11-05 11:57:52 +08:00
|
|
|
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|number[]} 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)
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
tab.upsertValueEntries({ server: connName, db, key, type: 'zset', entries: vs })
|
2023-11-05 11:57:52 +08:00
|
|
|
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|number[]} 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
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
if (!isEmpty(updated)) {
|
|
|
|
tab.upsertValueEntries({ server: connName, db, key, type: 'zset', entries: updated })
|
|
|
|
}
|
|
|
|
if (!isEmpty(removed)) {
|
|
|
|
tab.removeValueEntries({ server: connName, db, key, type: 'zset', entries: removed })
|
|
|
|
}
|
2023-11-05 11:57:52 +08:00
|
|
|
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 {string|number[]} 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
|
2023-11-08 23:45:33 +08:00
|
|
|
if (!isEmpty(removed)) {
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.removeValueEntries({ server: connName, db, key, type: 'zset', entries: removed })
|
|
|
|
}
|
|
|
|
return { success }
|
2023-11-05 11:57:52 +08:00
|
|
|
} else {
|
|
|
|
return { success, msg }
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return { success: false, msg: e.message }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* insert new stream field item
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {string} id
|
|
|
|
* @param {string[]} values field1, value1, filed2, value2...
|
2023-11-08 23:45:33 +08:00
|
|
|
* @returns {Promise<{[msg]: string, success: boolean}>}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
|
|
|
async addStreamValue(connName, db, key, id, values) {
|
|
|
|
try {
|
|
|
|
const { data = {}, success, msg } = await AddStreamValue(connName, db, key, id, values)
|
|
|
|
if (success) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const { updateID } = data
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.upsertValueEntries({
|
|
|
|
server: connName,
|
|
|
|
db,
|
|
|
|
key,
|
|
|
|
type: 'stream',
|
|
|
|
entries: [{ id: updateID, value: values }],
|
|
|
|
})
|
|
|
|
return { success }
|
2023-11-05 11:57:52 +08:00
|
|
|
} else {
|
|
|
|
return { success: false, msg }
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return { success: false, msg: e.message }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* remove stream field
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {string[]|string} ids
|
2023-11-08 23:45:33 +08:00
|
|
|
* @returns {Promise<{[msg]: {}, success: boolean}>}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
|
|
|
async removeStreamValues(connName, db, key, ids) {
|
|
|
|
if (typeof ids === 'string') {
|
|
|
|
ids = [ids]
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const { data = {}, success, msg } = await RemoveStreamValues(connName, db, key, ids)
|
|
|
|
if (success) {
|
2023-11-08 23:45:33 +08:00
|
|
|
const tab = useTabStore()
|
|
|
|
tab.removeValueEntries({ server: connName, db, key, type: 'stream', entries: ids })
|
|
|
|
return { success }
|
2023-11-05 11:57:52 +08:00
|
|
|
} 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
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-11-09 00:30:07 +08:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} key
|
|
|
|
* @param {string} newKey
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_renameKeyNode(connName, db, key, newKey) {
|
|
|
|
const nodeMap = this._getNodeMap(connName, db)
|
|
|
|
const nodeKey = `${ConnectionType.RedisValue}/${key}`
|
|
|
|
const newNodeKey = `${ConnectionType.RedisValue}/${newKey}`
|
|
|
|
const node = nodeMap.get(nodeKey)
|
|
|
|
if (node != null) {
|
|
|
|
// replace node map item
|
|
|
|
const separator = this._getSeparator(connName)
|
|
|
|
node.label = last(split(newKey, separator))
|
|
|
|
node.key = `${connName}/db${db}#${newNodeKey}`
|
|
|
|
node.redisKey = newKey
|
|
|
|
nodeMap[newNodeKey] = node
|
|
|
|
nodeMap.delete(nodeKey)
|
|
|
|
// replace key set item
|
|
|
|
const keySet = this._getKeySet(connName, db)
|
|
|
|
keySet.delete(key)
|
|
|
|
keySet.add(newKey)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2023-11-05 11:57:52 +08:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} [key]
|
|
|
|
* @param {boolean} [isLayer]
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_deleteKeyNode(connName, db, key, isLayer) {
|
|
|
|
const dbRoot = this.getDatabase(connName, db) || {}
|
|
|
|
const separator = this._getSeparator(connName)
|
|
|
|
|
|
|
|
if (dbRoot == null) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
const nodeMap = this._getNodeMap(connName, db)
|
|
|
|
const keySet = this._getKeySet(connName, db)
|
|
|
|
if (isLayer === true) {
|
|
|
|
this._deleteChildrenKeyNodes(nodeMap, keySet, key)
|
|
|
|
}
|
|
|
|
if (isEmpty(key)) {
|
|
|
|
// clear all key nodes
|
|
|
|
dbRoot.children = []
|
|
|
|
dbRoot.keys = 0
|
|
|
|
} else {
|
|
|
|
const keyParts = split(key, 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 = nodeMap.get(`${ConnectionType.RedisKey}/${join(parentKey, separator)}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
// not found parent node
|
|
|
|
if (parentNode == null) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
remove(parentNode.children, {
|
|
|
|
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), separator)
|
|
|
|
if (i > 0) {
|
|
|
|
const anceNode = nodeMap.get(`${ConnectionType.RedisKey}/${anceKey}`)
|
|
|
|
const redisKey = join(slice(keyParts, 0, i + 1), separator)
|
|
|
|
remove(anceNode.children, { type: ConnectionType.RedisKey, redisKey })
|
|
|
|
|
|
|
|
if (isEmpty(anceNode.children)) {
|
|
|
|
nodeMap.delete(`${ConnectionType.RedisKey}/${anceKey}`)
|
|
|
|
keySet.delete(anceNode.redisKeyCode || anceNode.redisKey)
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// last one, remove from db node
|
|
|
|
remove(dbRoot.children, { type: ConnectionType.RedisKey, redisKey: keyParts[0] })
|
|
|
|
const node = nodeMap.get(`${ConnectionType.RedisValue}/${keyParts[0]}`)
|
|
|
|
if (node != null) {
|
|
|
|
nodeMap.delete(`${ConnectionType.RedisValue}/${keyParts[0]}`)
|
|
|
|
keySet.delete(node.redisKeyCode || node.redisKey)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* delete node and all it's children from nodeMap
|
|
|
|
* @param {Map<string, DatabaseItem>} nodeMap
|
|
|
|
* @param {Set<string|number[]>} keySet
|
|
|
|
* @param {string} [key] clean nodeMap if key is empty
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
_deleteChildrenKeyNodes(nodeMap, keySet, key) {
|
|
|
|
if (isEmpty(key)) {
|
|
|
|
nodeMap.clear()
|
|
|
|
keySet.clear()
|
|
|
|
} else {
|
|
|
|
const mapKey = `${ConnectionType.RedisKey}/${key}`
|
|
|
|
const node = nodeMap.get(mapKey)
|
|
|
|
for (const child of node.children || []) {
|
|
|
|
if (child.type === ConnectionType.RedisValue) {
|
|
|
|
if (!nodeMap.delete(`${ConnectionType.RedisValue}/${child.redisKey}`)) {
|
|
|
|
console.warn('delete:', `${ConnectionType.RedisValue}/${child.redisKey}`)
|
|
|
|
}
|
|
|
|
keySet.delete(child.redisKeyCode || child.redisKey)
|
|
|
|
} else if (child.type === ConnectionType.RedisKey) {
|
|
|
|
this._deleteChildrenKeyNodes(nodeMap, keySet, child.redisKey)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!nodeMap.delete(mapKey)) {
|
|
|
|
console.warn('delete map key', mapKey)
|
|
|
|
}
|
|
|
|
keySet.delete(node.redisKeyCode || node.redisKey)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* delete redis key
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string|number[]} key
|
|
|
|
* @param {boolean} [soft] do not try to remove from redis if true, just remove from tree data
|
|
|
|
* @returns {Promise<boolean>}
|
|
|
|
*/
|
|
|
|
async deleteKey(connName, db, key, soft) {
|
|
|
|
try {
|
2023-11-05 13:00:03 +08:00
|
|
|
let deleteCount = 0
|
2023-11-05 11:57:52 +08:00
|
|
|
if (soft !== true) {
|
2023-11-05 13:00:03 +08:00
|
|
|
const { data } = await DeleteKey(connName, db, key)
|
|
|
|
deleteCount = get(data, 'deleteCount', 0)
|
2023-11-05 11:57:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const k = nativeRedisKey(key)
|
|
|
|
// update tree view data
|
|
|
|
this._deleteKeyNode(connName, db, k)
|
|
|
|
this._tidyNode(connName, db, k, true)
|
2023-11-05 13:00:03 +08:00
|
|
|
this._updateDBMaxKeys(connName, db, -deleteCount)
|
2023-11-05 11:57:52 +08:00
|
|
|
|
|
|
|
// set tab content empty
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.emptyTab(connName)
|
|
|
|
return true
|
|
|
|
} finally {
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* delete keys with prefix
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} prefix
|
|
|
|
* @param {boolean} async
|
|
|
|
* @returns {Promise<boolean>}
|
|
|
|
*/
|
|
|
|
async deleteKeyPrefix(connName, db, prefix, async) {
|
|
|
|
if (isEmpty(prefix)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
if (!endsWith(prefix, '*')) {
|
|
|
|
prefix += '*'
|
|
|
|
}
|
|
|
|
const { data, success, msg } = await DeleteKey(connName, db, prefix, async)
|
|
|
|
if (success) {
|
|
|
|
// const { deleted: keys = [] } = data
|
|
|
|
// for (const key of keys) {
|
|
|
|
// await this._deleteKeyNode(connName, db, key)
|
|
|
|
// }
|
2023-11-05 13:00:03 +08:00
|
|
|
const deleteCount = get(data, 'deleteCount', 0)
|
2023-11-05 11:57:52 +08:00
|
|
|
const separator = this._getSeparator(connName)
|
|
|
|
if (endsWith(prefix, '*')) {
|
|
|
|
prefix = prefix.substring(0, prefix.length - 1)
|
|
|
|
}
|
|
|
|
if (endsWith(prefix, separator)) {
|
|
|
|
prefix = prefix.substring(0, prefix.length - 1)
|
|
|
|
}
|
|
|
|
this._deleteKeyNode(connName, db, prefix, true)
|
|
|
|
this._tidyNode(connName, db, prefix, true)
|
2023-11-05 13:00:03 +08:00
|
|
|
this._updateDBMaxKeys(connName, db, -deleteCount)
|
2023-11-05 11:57:52 +08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* flush database
|
|
|
|
* @param connName
|
|
|
|
* @param db
|
|
|
|
* @param async
|
|
|
|
* @return {Promise<boolean>}
|
|
|
|
*/
|
|
|
|
async flushDatabase(connName, db, async) {
|
|
|
|
try {
|
|
|
|
const { success = false } = await FlushDB(connName, db, async)
|
|
|
|
|
|
|
|
if (success === true) {
|
|
|
|
// update tree view data
|
|
|
|
this._deleteKeyNode(connName, db)
|
2023-11-10 11:52:54 +08:00
|
|
|
this._setDBMaxKeys(connName, db, 0)
|
2023-11-05 11:57:52 +08:00
|
|
|
// set tab content empty
|
|
|
|
const tab = useTabStore()
|
|
|
|
tab.emptyTab(connName)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* rename key
|
|
|
|
* @param {string} connName
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} key
|
|
|
|
* @param {string} newKey
|
2023-11-08 23:45:33 +08:00
|
|
|
* @returns {Promise<{[msg]: string, success: boolean, [nodeKey]: string}>}
|
2023-11-05 11:57:52 +08:00
|
|
|
*/
|
|
|
|
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
|
2023-11-09 00:30:07 +08:00
|
|
|
this._renameKeyNode(connName, db, key, newKey)
|
2023-11-08 23:45:33 +08:00
|
|
|
return { success: true, nodeKey: `${connName}/db${db}#${ConnectionType.RedisValue}/${newKey}` }
|
2023-11-05 11:57:52 +08:00
|
|
|
} else {
|
|
|
|
return { success: false, msg }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get command history
|
|
|
|
* @param {number} [pageNo]
|
|
|
|
* @param {number} [pageSize]
|
|
|
|
* @returns {Promise<HistoryItem[]>}
|
|
|
|
*/
|
|
|
|
async getCmdHistory(pageNo, pageSize) {
|
|
|
|
if (pageNo === undefined || pageSize === undefined) {
|
|
|
|
pageNo = -1
|
|
|
|
pageSize = -1
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const { success, data = { list: [] } } = await GetCmdHistory(pageNo, pageSize)
|
|
|
|
const { list } = data
|
|
|
|
return list
|
|
|
|
} catch {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* clean cmd history
|
|
|
|
* @return {Promise<boolean>}
|
|
|
|
*/
|
|
|
|
async cleanCmdHistory() {
|
|
|
|
try {
|
|
|
|
const { success } = await CleanCmdHistory()
|
|
|
|
return success === true
|
|
|
|
} catch {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get slow log list
|
|
|
|
* @param {string} server
|
|
|
|
* @param {number} db
|
|
|
|
* @param {number} num
|
|
|
|
* @return {Promise<[]>}
|
|
|
|
*/
|
|
|
|
async getSlowLog(server, db, num) {
|
|
|
|
try {
|
|
|
|
const { success, data = { list: [] } } = await GetSlowLogs(server, db, num)
|
|
|
|
const { list } = data
|
|
|
|
return list
|
|
|
|
} catch {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get key filter pattern and filter type
|
|
|
|
* @param {string} server
|
|
|
|
* @param {number} db
|
|
|
|
* @returns {{match: string, type: string}}
|
|
|
|
*/
|
|
|
|
getKeyFilter(server, db) {
|
|
|
|
let match, type
|
|
|
|
const key = `${server}#${db}`
|
|
|
|
if (!this.keyFilter.hasOwnProperty(key)) {
|
|
|
|
// get default key filter from connection profile
|
|
|
|
const conn = useConnectionStore()
|
|
|
|
match = conn.getDefaultKeyFilter(server)
|
|
|
|
} else {
|
|
|
|
match = this.keyFilter[key] || '*'
|
|
|
|
}
|
|
|
|
type = this.typeFilter[`${server}#${db}`] || ''
|
|
|
|
return {
|
|
|
|
match,
|
|
|
|
type: toUpper(type),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* set key filter
|
|
|
|
* @param {string} server
|
|
|
|
* @param {number} db
|
|
|
|
* @param {string} pattern
|
|
|
|
* @param {string} [type]
|
|
|
|
*/
|
|
|
|
setKeyFilter(server, db, pattern, type) {
|
|
|
|
this.keyFilter[`${server}#${db}`] = pattern || '*'
|
|
|
|
this.typeFilter[`${server}#${db}`] = types[toUpper(type)] || ''
|
|
|
|
},
|
|
|
|
|
|
|
|
removeKeyFilter(server, db) {
|
|
|
|
this.keyFilter[`${server}#${db}`] = '*'
|
|
|
|
delete this.typeFilter[`${server}#${db}`]
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
export default useBrowserStore
|