feat: add key type filter setting
perf: add closing connection status in edit connection dialog
This commit is contained in:
parent
41e5ecfad2
commit
c4f1a2e178
|
@ -347,24 +347,30 @@ func (c *connectionService) ServerInfo(name string) (resp types.JSResp) {
|
|||
|
||||
// OpenDatabase open select database, and list all keys
|
||||
// @param path contain connection name and db name
|
||||
func (c *connectionService) OpenDatabase(connName string, db int, match string) (resp types.JSResp) {
|
||||
return c.ScanKeys(connName, db, match)
|
||||
func (c *connectionService) OpenDatabase(connName string, db int, match string, keyType string) (resp types.JSResp) {
|
||||
return c.ScanKeys(connName, db, match, keyType)
|
||||
}
|
||||
|
||||
// ScanKeys scan all keys
|
||||
func (c *connectionService) ScanKeys(connName string, db int, match string) (resp types.JSResp) {
|
||||
func (c *connectionService) ScanKeys(connName string, db int, match, keyType string) (resp types.JSResp) {
|
||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
||||
if err != nil {
|
||||
resp.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
filterType := len(keyType) > 0
|
||||
|
||||
var keys []string
|
||||
//keys := map[string]keyItem{}
|
||||
var cursor uint64
|
||||
for {
|
||||
var loadedKey []string
|
||||
if filterType {
|
||||
loadedKey, cursor, err = rdb.ScanType(ctx, cursor, match, 10000, keyType).Result()
|
||||
} else {
|
||||
loadedKey, cursor, err = rdb.Scan(ctx, cursor, match, 10000).Result()
|
||||
}
|
||||
if err != nil {
|
||||
resp.Msg = err.Error()
|
||||
return
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { types, validType } from '../../consts/support_redis_type.js'
|
||||
import { typesColor, validType } from '../../consts/support_redis_type.js'
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
|
@ -12,30 +12,22 @@ const props = defineProps({
|
|||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: 'white',
|
||||
},
|
||||
size: String,
|
||||
})
|
||||
|
||||
const color = {
|
||||
[types.STRING]: '#626aef',
|
||||
[types.HASH]: '#576bfa',
|
||||
[types.LIST]: '#34b285',
|
||||
[types.SET]: '#bb7d52',
|
||||
[types.ZSET]: '#d053a5',
|
||||
}
|
||||
|
||||
const backgroundColor = computed(() => {
|
||||
return color[props.type]
|
||||
return typesColor[props.type]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-tag
|
||||
:bordered="false"
|
||||
:color="{ color: backgroundColor, textColor: 'white' }"
|
||||
:color="{ color: backgroundColor, textColor: props.color }"
|
||||
:size="props.size"
|
||||
class="redis-type-tag"
|
||||
:class="[props.size === 'small' ? 'redis-type-tag-small' : 'redis-type-tag']"
|
||||
strong
|
||||
>
|
||||
{{ props.type }}
|
||||
|
@ -47,4 +39,8 @@ const backgroundColor = computed(() => {
|
|||
.redis-type-tag {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.redis-type-tag-small {
|
||||
padding: 0 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -24,7 +24,7 @@ const props = defineProps({
|
|||
|
||||
const connectionStore = useConnectionStore()
|
||||
const dialogStore = useDialogStore()
|
||||
const keyType = redisTypes.LIST
|
||||
const keyType = redisTypes.SET
|
||||
const currentEditRow = ref({
|
||||
no: 0,
|
||||
value: null,
|
||||
|
|
|
@ -9,7 +9,7 @@ import Close from '../icons/Close.vue'
|
|||
import useConnectionStore from '../../stores/connections.js'
|
||||
|
||||
/**
|
||||
* Dialog for create or edit connection
|
||||
* Dialog for new or edit connection
|
||||
*/
|
||||
|
||||
const dialogStore = useDialog()
|
||||
|
@ -29,6 +29,12 @@ const generalFormRules = () => {
|
|||
}
|
||||
}
|
||||
const isEditMode = computed(() => !isEmpty(editName.value))
|
||||
const closingConnection = computed(() => {
|
||||
if (isEmpty(editName.value)) {
|
||||
return false
|
||||
}
|
||||
return connectionStore.isConnected(editName.value)
|
||||
})
|
||||
|
||||
const groupOptions = computed(() => {
|
||||
const options = map(connectionStore.groups, (group) => ({
|
||||
|
@ -152,6 +158,7 @@ const onClose = () => {
|
|||
preset="dialog"
|
||||
transform-origin="center"
|
||||
>
|
||||
<n-spin :show="closingConnection">
|
||||
<n-tabs v-model:value="tab" type="line" animated>
|
||||
<n-tab-pane :tab="$t('general')" display-directive="show" name="general">
|
||||
<n-form
|
||||
|
@ -172,7 +179,12 @@ const onClose = () => {
|
|||
<n-form-item :label="$t('conn_addr')" path="addr" required>
|
||||
<n-input v-model:value="generalForm.addr" :placeholder="$t('conn_addr_tip')" />
|
||||
<n-text style="width: 40px; text-align: center">:</n-text>
|
||||
<n-input-number v-model:value="generalForm.port" :max="65535" :min="1" style="width: 200px" />
|
||||
<n-input-number
|
||||
v-model:value="generalForm.port"
|
||||
:max="65535"
|
||||
:min="1"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('conn_pwd')" path="password">
|
||||
<n-input
|
||||
|
@ -199,7 +211,10 @@ const onClose = () => {
|
|||
label-placement="left"
|
||||
>
|
||||
<n-form-item :label="$t('conn_advn_filter')" path="defaultFilter">
|
||||
<n-input v-model:value="generalForm.defaultFilter" :placeholder="$t('conn_advn_filter_tip')" />
|
||||
<n-input
|
||||
v-model:value="generalForm.defaultFilter"
|
||||
:placeholder="$t('conn_advn_filter_tip')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('conn_advn_separator')" path="keySeparator">
|
||||
<n-input
|
||||
|
@ -246,14 +261,17 @@ const onClose = () => {
|
|||
<n-alert v-if="showTestConnFailResult" title="" type="error">
|
||||
{{ $t('conn_test_fail') }}: {{ testResult }}
|
||||
</n-alert>
|
||||
</n-spin>
|
||||
|
||||
<template #action>
|
||||
<div class="flex-item-expand">
|
||||
<n-button :loading="testing" @click="onTestConnection">{{ $t('conn_test') }}</n-button>
|
||||
<n-button :loading="testing" :disabled="closingConnection" @click="onTestConnection">
|
||||
{{ $t('conn_test') }}
|
||||
</n-button>
|
||||
</div>
|
||||
<div class="flex-item n-dialog__action">
|
||||
<n-button @click="onClose">{{ $t('cancel') }}</n-button>
|
||||
<n-button type="primary" @click="onSaveConnection">
|
||||
<n-button :disabled="closingConnection" @click="onClose">{{ $t('cancel') }}</n-button>
|
||||
<n-button type="primary" :disabled="closingConnection" @click="onSaveConnection">
|
||||
{{ isEditMode ? $t('update') : $t('confirm') }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
<script setup>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import useDialog from '../../stores/dialog'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useConnectionStore from '../../stores/connections.js'
|
||||
import { types } from '../../consts/support_redis_type.js'
|
||||
|
||||
const i18n = useI18n()
|
||||
const filterForm = reactive({
|
||||
server: '',
|
||||
db: 0,
|
||||
type: '',
|
||||
pattern: '',
|
||||
})
|
||||
const filterFormRef = ref(null)
|
||||
const typeOptions = computed(() => {
|
||||
const options = Object.keys(types).map((t) => ({
|
||||
value: t,
|
||||
label: t,
|
||||
}))
|
||||
options.splice(0, 0, {
|
||||
value: '',
|
||||
label: i18n.t('all'),
|
||||
})
|
||||
return options
|
||||
})
|
||||
|
||||
const formLabelWidth = '100px'
|
||||
const dialogStore = useDialog()
|
||||
|
@ -18,9 +31,10 @@ watch(
|
|||
() => dialogStore.keyFilterDialogVisible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
const { server, db, pattern } = dialogStore.keyFilterParam
|
||||
const { server, db, type, pattern } = dialogStore.keyFilterParam
|
||||
filterForm.server = server
|
||||
filterForm.db = db || 0
|
||||
filterForm.type = type || ''
|
||||
filterForm.pattern = pattern || '*'
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +42,8 @@ watch(
|
|||
|
||||
const connectionStore = useConnectionStore()
|
||||
const onConfirm = () => {
|
||||
const { server, db, pattern } = filterForm
|
||||
connectionStore.setKeyFilter(server, db, pattern)
|
||||
const { server, db, type, pattern } = filterForm
|
||||
connectionStore.setKeyFilter(server, db, pattern, type)
|
||||
connectionStore.reopenDatabase(server, db)
|
||||
}
|
||||
|
||||
|
@ -71,6 +85,9 @@ const onClose = () => {
|
|||
<n-form-item :label="$t('db_index')" path="db">
|
||||
<n-text>{{ filterForm.db }}</n-text>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('type')" path="type" required>
|
||||
<n-select v-model:value="filterForm.type" :options="typeOptions" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('filter_pattern')" required>
|
||||
<n-input-group>
|
||||
<n-tooltip>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { computed, h, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { ConnectionType } from '../../consts/connection_type.js'
|
||||
import { NIcon, NTag, useDialog, useMessage } from 'naive-ui'
|
||||
import { NIcon, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
|
||||
import Key from '../icons/Key.vue'
|
||||
import ToggleDb from '../icons/ToggleDb.vue'
|
||||
import { get, indexOf, isEmpty, remove } from 'lodash'
|
||||
|
@ -20,6 +20,7 @@ import ToggleServer from '../icons/ToggleServer.vue'
|
|||
import Unlink from '../icons/Unlink.vue'
|
||||
import Filter from '../icons/Filter.vue'
|
||||
import Close from '../icons/Close.vue'
|
||||
import { typesColor } from '../../consts/support_redis_type.js'
|
||||
|
||||
const props = defineProps({
|
||||
server: String,
|
||||
|
@ -274,26 +275,51 @@ const renderLabel = ({ option }) => {
|
|||
}
|
||||
|
||||
const renderSuffix = ({ option }) => {
|
||||
// return h(NButton,
|
||||
// { text: true, type: 'primary' },
|
||||
// { default: () => h(Key) })
|
||||
if (option.type === ConnectionType.RedisDB) {
|
||||
const { name: server, db } = option
|
||||
const filterPattern = connectionStore.getKeyFilter(server, db)
|
||||
if (!isEmpty(filterPattern) && filterPattern !== '*') {
|
||||
return h(
|
||||
let { match: matchPattern, type: typeFilter } = connectionStore.getKeyFilter(server, db)
|
||||
const filterNodes = []
|
||||
// type filter tag
|
||||
if (!isEmpty(typeFilter)) {
|
||||
filterNodes.push(
|
||||
h(
|
||||
NTag,
|
||||
{
|
||||
size: 'small',
|
||||
closable: true,
|
||||
bordered: false,
|
||||
color: { color: typesColor[typeFilter], textColor: 'white' },
|
||||
onClose: () => {
|
||||
// remove type filter
|
||||
connectionStore.setKeyFilter(server, db, matchPattern)
|
||||
connectionStore.reopenDatabase(server, db)
|
||||
},
|
||||
},
|
||||
{ default: () => typeFilter }
|
||||
)
|
||||
)
|
||||
}
|
||||
// match pattern tag
|
||||
if (!isEmpty(matchPattern) && matchPattern !== '*') {
|
||||
filterNodes.push(
|
||||
h(
|
||||
NTag,
|
||||
{
|
||||
bordered: false,
|
||||
closable: true,
|
||||
size: 'small',
|
||||
onClose: () => {
|
||||
connectionStore.removeKeyFilter(server, db)
|
||||
// remove key match pattern
|
||||
connectionStore.setKeyFilter(server, db, '*', typeFilter)
|
||||
connectionStore.reopenDatabase(server, db)
|
||||
},
|
||||
},
|
||||
{ default: () => filterPattern }
|
||||
{ default: () => matchPattern }
|
||||
)
|
||||
)
|
||||
}
|
||||
if (filterNodes.length > 0) {
|
||||
return h(NSpace, { align: 'center', inline: true, size: 2 }, () => filterNodes)
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
@ -379,8 +405,8 @@ const handleSelectContextMenu = (key) => {
|
|||
dialogStore.openNewKeyDialog(redisKey, props.server, db)
|
||||
break
|
||||
case 'db_filter':
|
||||
const pattern = connectionStore.getKeyFilter(props.server, db)
|
||||
dialogStore.openKeyFilterDialog(props.server, db, pattern)
|
||||
const { match: pattern, type } = connectionStore.getKeyFilter(props.server, db)
|
||||
dialogStore.openKeyFilterDialog(props.server, db, pattern, type)
|
||||
break
|
||||
case 'key_reload':
|
||||
connectionStore.loadKeys(props.server, db, redisKey)
|
||||
|
|
|
@ -241,11 +241,8 @@ const handleSelectContextMenu = (key) => {
|
|||
// ask for close relevant connections before edit
|
||||
if (connectionStore.isConnected(name)) {
|
||||
confirmDialog.warning(i18n.t('edit_close_confirm'), () => {
|
||||
connectionStore.closeConnection(name).then((success) => {
|
||||
if (success) {
|
||||
connectionStore.closeConnection(name)
|
||||
dialogStore.openEditDialog(name)
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
dialogStore.openEditDialog(name)
|
||||
|
@ -285,7 +282,7 @@ const findSiblingsAndIndex = (node, nodes) => {
|
|||
return [null, null]
|
||||
}
|
||||
|
||||
// delay save until stop drop after 2 seconds
|
||||
// delay save until drop stopped after 2 seconds
|
||||
const saveSort = debounce(connectionStore.saveConnectionSorted, 2000, { trailing: true })
|
||||
const handleDrop = ({ node, dragNode, dropPosition }) => {
|
||||
const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(dragNode, connectionStore.connections)
|
||||
|
|
|
@ -6,6 +6,14 @@ export const types = {
|
|||
ZSET: 'ZSET',
|
||||
}
|
||||
|
||||
export const typesColor = {
|
||||
[types.STRING]: '#5A96E3',
|
||||
[types.HASH]: '#9575DE',
|
||||
[types.LIST]: '#7A9D54',
|
||||
[types.SET]: '#F3AA60',
|
||||
[types.ZSET]: '#FF6666',
|
||||
}
|
||||
|
||||
// export const typesName = Object.fromEntries(Object.entries(types).map(([key, value]) => [key, value.name]))
|
||||
|
||||
export const validType = (t) => {
|
||||
|
|
|
@ -149,8 +149,8 @@
|
|||
"unit_day": "天",
|
||||
"unit_hour": "小时",
|
||||
"unit_minute": "分钟",
|
||||
"all_info": "所有信息",
|
||||
"all": "所有",
|
||||
"all_info": "全部信息",
|
||||
"all": "全部",
|
||||
"launch_log": "运行日志",
|
||||
"filter_server": "筛选服务器",
|
||||
"filter_keyword": "筛选关键字",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { endsWith, findIndex, get, isEmpty, size, split, uniq } from 'lodash'
|
||||
import { endsWith, findIndex, get, isEmpty, size, split, toUpper, uniq } from 'lodash'
|
||||
import {
|
||||
AddHashField,
|
||||
AddListItem,
|
||||
|
@ -31,6 +31,7 @@ import {
|
|||
} from '../../wailsjs/go/services/connectionService.js'
|
||||
import { ConnectionType } from '../consts/connection_type.js'
|
||||
import useTabStore from './tab.js'
|
||||
import { types } from '../consts/support_redis_type.js'
|
||||
|
||||
const useConnectionStore = defineStore('connections', {
|
||||
/**
|
||||
|
@ -62,6 +63,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
* @property {Object} serverStats
|
||||
* @property {Object.<string, ConnectionProfile>} serverProfile
|
||||
* @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, DatabaseItem[]>} databases
|
||||
* @property {Object.<string, Map<string, DatabaseItem>>} nodeMap key format likes 'server#db', children key format likes 'key#type'
|
||||
*/
|
||||
|
@ -90,6 +92,7 @@ const useConnectionStore = defineStore('connections', {
|
|||
serverStats: {}, // current server status info
|
||||
serverProfile: {}, // all server profile
|
||||
keyFilter: {}, // all key filters in opened connections group by server+db
|
||||
typeFilter: {}, // all key type filters in opened connections group by server+db
|
||||
databases: {}, // all databases in opened connections group by server name
|
||||
nodeMap: {}, // all nodes in opened connections group by server#db and type/key
|
||||
}),
|
||||
|
@ -434,8 +437,8 @@ const useConnectionStore = defineStore('connections', {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
async openDatabase(connName, db) {
|
||||
const filterPattern = this.getKeyFilter(connName, db)
|
||||
const { data, success, msg } = await OpenDatabase(connName, db, filterPattern)
|
||||
const { match: filterPattern, type: keyType } = this.getKeyFilter(connName, db)
|
||||
const { data, success, msg } = await OpenDatabase(connName, db, filterPattern, keyType)
|
||||
if (!success) {
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
@ -1247,17 +1250,24 @@ const useConnectionStore = defineStore('connections', {
|
|||
},
|
||||
|
||||
/**
|
||||
* get key filter pattern
|
||||
* get key filter pattern and filter type
|
||||
* @param {string} server
|
||||
* @param {number} db
|
||||
* @returns {string}
|
||||
* @returns {{match: string, type: string}}
|
||||
*/
|
||||
getKeyFilter(server, db) {
|
||||
let match, type
|
||||
const key = `${server}#${db}`
|
||||
if (!this.keyFilter.hasOwnProperty(key)) {
|
||||
return this.keyFilter[`${server}#-1`] || '*'
|
||||
match = this.keyFilter[`${server}#-1`] || '*'
|
||||
} else {
|
||||
match = this.keyFilter[key] || '*'
|
||||
}
|
||||
type = this.typeFilter[`${server}#${db}`] || ''
|
||||
return {
|
||||
match,
|
||||
type: toUpper(type),
|
||||
}
|
||||
return this.keyFilter[key] || '*'
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1265,13 +1275,16 @@ const useConnectionStore = defineStore('connections', {
|
|||
* @param {string} server
|
||||
* @param {number} db
|
||||
* @param {string} pattern
|
||||
* @param {string} [type]
|
||||
*/
|
||||
setKeyFilter(server, db, pattern) {
|
||||
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}`]
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -24,6 +24,7 @@ const useDialogStore = defineStore('dialog', {
|
|||
keyFilterParam: {
|
||||
server: '',
|
||||
db: 0,
|
||||
type: '',
|
||||
pattern: '*',
|
||||
},
|
||||
keyFilterDialogVisible: false,
|
||||
|
@ -85,12 +86,14 @@ const useDialogStore = defineStore('dialog', {
|
|||
*
|
||||
* @param {string} server
|
||||
* @param {number} db
|
||||
* @param {string} pattern
|
||||
* @param {string} [pattern]
|
||||
* @param {string} [type]
|
||||
*/
|
||||
openKeyFilterDialog(server, db, pattern) {
|
||||
openKeyFilterDialog(server, db, pattern, type) {
|
||||
this.keyFilterParam.server = server
|
||||
this.keyFilterParam.db = db
|
||||
this.keyFilterParam.pattern = '*'
|
||||
this.keyFilterParam.type = type || ''
|
||||
this.keyFilterParam.pattern = pattern || '*'
|
||||
this.keyFilterDialogVisible = true
|
||||
},
|
||||
closeKeyFilterDialog() {
|
||||
|
|
Loading…
Reference in New Issue