feat: add filter database option #18

refactor: tidy struct of translation file
This commit is contained in:
tiny-craft 2023-10-07 02:08:41 +08:00
parent 7937b5b0f8
commit bb1d9f316b
8 changed files with 214 additions and 98 deletions

View File

@ -9,6 +9,7 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"net" "net"
"os" "os"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -261,11 +262,16 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
return return
} }
// get total databases // get connection config
selConn := c.conns.GetConnection(name)
totaldb := 16 totaldb := 16
if config, err := rdb.ConfigGet(ctx, "databases").Result(); err == nil { if selConn.DBFilterType == "none" {
if total, err := strconv.Atoi(config["databases"]); err == nil { // get total databases
totaldb = total if config, err := rdb.ConfigGet(ctx, "databases").Result(); err == nil {
if total, err := strconv.Atoi(config["databases"]); err == nil {
totaldb = total
}
} }
} }
@ -278,21 +284,37 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
// Parse all db, response content like below // Parse all db, response content like below
var dbs []types.ConnectionDB var dbs []types.ConnectionDB
info := c.parseInfo(res) info := c.parseInfo(res)
for i := 0; i < totaldb; i++ { queryDB := func(idx int) types.ConnectionDB {
dbName := "db" + strconv.Itoa(i) dbName := "db" + strconv.Itoa(idx)
dbInfoStr := info["Keyspace"][dbName] dbInfoStr := info["Keyspace"][dbName]
if len(dbInfoStr) > 0 { if len(dbInfoStr) > 0 {
dbInfo := c.parseDBItemInfo(dbInfoStr) dbInfo := c.parseDBItemInfo(dbInfoStr)
dbs = append(dbs, types.ConnectionDB{ return types.ConnectionDB{
Name: dbName, Name: dbName,
Keys: dbInfo["keys"], Keys: dbInfo["keys"],
Expires: dbInfo["expires"], Expires: dbInfo["expires"],
AvgTTL: dbInfo["avg_ttl"], AvgTTL: dbInfo["avg_ttl"],
}) }
} else { } else {
dbs = append(dbs, types.ConnectionDB{ return types.ConnectionDB{
Name: dbName, Name: dbName,
}) }
}
}
switch selConn.DBFilterType {
case "none":
for idx := 0; idx < totaldb; idx++ {
dbs = append(dbs, queryDB(idx))
}
case "show":
for _, idx := range selConn.DBFilterList {
dbs = append(dbs, queryDB(idx))
}
case "hide":
for idx := 0; idx < totaldb; idx++ {
if !slices.Contains(selConn.DBFilterList, idx) {
dbs = append(dbs, queryDB(idx))
}
} }
} }
@ -350,7 +372,7 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
}) })
})) }))
if _, err := rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil { if _, err = rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
return nil, nil, errors.New("can not connect to redis server:" + err.Error()) return nil, nil, errors.New("can not connect to redis server:" + err.Error())
} }
var cancelFunc context.CancelFunc var cancelFunc context.CancelFunc

View File

@ -34,6 +34,8 @@ func (c *ConnectionsStorage) defaultConnectionItem() types.ConnectionConfig {
KeySeparator: ":", KeySeparator: ":",
ConnTimeout: 60, ConnTimeout: 60,
ExecTimeout: 60, ExecTimeout: 60,
DBFilterType: "none",
DBFilterList: []int{},
MarkColor: "", MarkColor: "",
} }
} }

View File

@ -13,6 +13,8 @@ type ConnectionConfig struct {
KeySeparator string `json:"keySeparator,omitempty" yaml:"key_separator,omitempty"` KeySeparator string `json:"keySeparator,omitempty" yaml:"key_separator,omitempty"`
ConnTimeout int `json:"connTimeout,omitempty" yaml:"conn_timeout,omitempty"` ConnTimeout int `json:"connTimeout,omitempty" yaml:"conn_timeout,omitempty"`
ExecTimeout int `json:"execTimeout,omitempty" yaml:"exec_timeout,omitempty"` ExecTimeout int `json:"execTimeout,omitempty" yaml:"exec_timeout,omitempty"`
DBFilterType string `json:"dbFilterType" yaml:"db_filter_type,omitempty"`
DBFilterList []int `json:"dbFilterList" yaml:"db_filter_list,omitempty"`
MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"` MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"`
SSH ConnectionSSH `json:"ssh,omitempty" yaml:"ssh,omitempty"` SSH ConnectionSSH `json:"ssh,omitempty" yaml:"ssh,omitempty"`
} }

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { every, get, includes, isEmpty, map } from 'lodash' import { every, get, includes, isEmpty, map, sortBy, toNumber } from 'lodash'
import { computed, nextTick, ref, watch } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { SelectKeyFile, TestConnection } from 'wailsjs/go/services/connectionService.js' import { SelectKeyFile, TestConnection } from 'wailsjs/go/services/connectionService.js'
@ -56,12 +56,24 @@ const groupOptions = computed(() => {
return options return options
}) })
const dbFilterList = ref([])
const onUpdateDBFilterList = (list) => {
const dbList = []
for (const item of list) {
const idx = toNumber(item)
if (!isNaN(idx)) {
dbList.push(idx)
}
}
generalForm.value.dbFilterList = sortBy(dbList)
}
const sshLoginType = computed(() => { const sshLoginType = computed(() => {
return get(generalForm.value, 'ssh.loginType', 'pwd') return get(generalForm.value, 'ssh.loginType', 'pwd')
}) })
const onChoosePKFile = async () => { const onChoosePKFile = async () => {
const { success, data } = await SelectKeyFile(i18n.t('dialogue.connection.pkfile_selection_title')) const { success, data } = await SelectKeyFile(i18n.t('dialogue.connection.ssh.pkfile_selection_title'))
if (!success) { if (!success) {
generalForm.value.ssh.pkFile = '' generalForm.value.ssh.pkFile = ''
} else { } else {
@ -139,6 +151,7 @@ watch(
resetForm() resetForm()
editName.value = get(dialogStore.connParam, 'name', '') editName.value = get(dialogStore.connParam, 'name', '')
generalForm.value = dialogStore.connParam || connectionStore.newDefaultConnection() generalForm.value = dialogStore.connParam || connectionStore.newDefaultConnection()
dbFilterList.value = map(generalForm.value.dbFilterList, (item) => item + '')
generalForm.value.ssh.loginType = generalForm.value.ssh.loginType || 'pwd' generalForm.value.ssh.loginType = generalForm.value.ssh.loginType || 'pwd'
} }
}, },
@ -233,50 +246,91 @@ const onClose = () => {
:rules="generalFormRules()" :rules="generalFormRules()"
:show-require-mark="false" :show-require-mark="false"
label-placement="top"> label-placement="top">
<n-form-item :label="$t('dialogue.connection.advn_filter')" path="defaultFilter"> <n-grid :x-gap="10">
<n-input <n-form-item-gi
v-model:value="generalForm.defaultFilter" :span="12"
:placeholder="$t('dialogue.connection.advn_filter_tip')" /> :label="$t('dialogue.connection.advn.filter')"
</n-form-item> path="defaultFilter">
<n-form-item :label="$t('dialogue.connection.advn_separator')" path="keySeparator"> <n-input
<n-input v-model:value="generalForm.defaultFilter"
v-model:value="generalForm.keySeparator" :placeholder="$t('dialogue.connection.advn.filter_tip')" />
:placeholder="$t('dialogue.connection.advn_separator_tip')" /> </n-form-item-gi>
</n-form-item> <n-form-item-gi
<n-form-item :label="$t('dialogue.connection.advn_conn_timeout')" path="connTimeout"> :span="12"
<n-input-number v-model:value="generalForm.connTimeout" :max="999999" :min="1"> :label="$t('dialogue.connection.advn.separator')"
<template #suffix> path="keySeparator">
{{ $t('common.second') }} <n-input
</template> v-model:value="generalForm.keySeparator"
</n-input-number> :placeholder="$t('dialogue.connection.advn_separator_tip')" />
</n-form-item> </n-form-item-gi>
<n-form-item :label="$t('dialogue.connection.advn_exec_timeout')" path="execTimeout"> <n-form-item-gi
<n-input-number v-model:value="generalForm.execTimeout" :max="999999" :min="1"> :span="12"
<template #suffix> :label="$t('dialogue.connection.advn.conn_timeout')"
{{ $t('common.second') }} path="connTimeout">
</template> <n-input-number v-model:value="generalForm.connTimeout" :max="999999" :min="1">
</n-input-number> <template #suffix>
</n-form-item> {{ $t('common.second') }}
<n-form-item :label="$t('dialogue.connection.advn_mark_color')" path="markColor"> </template>
<div </n-input-number>
v-for="color in predefineColors" </n-form-item-gi>
:key="color" <n-form-item-gi
:class="{ :span="12"
'color-preset-item_selected': generalForm.markColor === color, :label="$t('dialogue.connection.advn.exec_timeout')"
}" path="execTimeout">
:style="{ backgroundColor: color }" <n-input-number v-model:value="generalForm.execTimeout" :max="999999" :min="1">
class="color-preset-item" <template #suffix>
@click="generalForm.markColor = color"> {{ $t('common.second') }}
<n-icon v-if="isEmpty(color)" :component="Close" size="24" /> </template>
</div> </n-input-number>
</n-form-item> </n-form-item-gi>
<n-form-item-gi :span="24" :label="$t('dialogue.connection.advn.dbfilter_type')">
<n-radio-group v-model:value="generalForm.dbFilterType">
<n-radio-button :label="$t('dialogue.connection.advn.dbfilter_all')" value="none" />
<n-radio-button
:label="$t('dialogue.connection.advn.dbfilter_show')"
value="show" />
<n-radio-button
:label="$t('dialogue.connection.advn.dbfilter_hide')"
value="hide" />
</n-radio-group>
</n-form-item-gi>
<n-form-item-gi :span="24" :label="$t('dialogue.connection.advn.dbfilter_input')">
<n-select
v-model:value="dbFilterList"
:disabled="generalForm.dbFilterType === 'none'"
filterable
multiple
tag
:placeholder="$t('dialogue.connection.advn.dbfilter_input_tip')"
:show-arrow="false"
:show="false"
:clearable="true"
@update:value="onUpdateDBFilterList" />
</n-form-item-gi>
<n-form-item-gi
:span="24"
:label="$t('dialogue.connection.advn.mark_color')"
path="markColor">
<div
v-for="color in predefineColors"
:key="color"
:class="{
'color-preset-item_selected': generalForm.markColor === color,
}"
:style="{ backgroundColor: color }"
class="color-preset-item"
@click="generalForm.markColor = color">
<n-icon v-if="isEmpty(color)" :component="Close" size="24" />
</div>
</n-form-item-gi>
</n-grid>
</n-form> </n-form>
</n-tab-pane> </n-tab-pane>
<n-tab-pane :tab="$t('dialogue.connection.ssh_tunnel')" display-directive="show" name="ssh"> <n-tab-pane :tab="$t('dialogue.connection.ssh.tunnel')" display-directive="show" name="ssh">
<n-form-item label-placement="left"> <n-form-item label-placement="left">
<n-checkbox v-model:checked="generalForm.ssh.enable" size="medium"> <n-checkbox v-model:checked="generalForm.ssh.enable" size="medium">
{{ $t('dialogue.connection.ssh_enable') }} {{ $t('dialogue.connection.ssh.enable') }}
</n-checkbox> </n-checkbox>
</n-form-item> </n-form-item>
<n-form <n-form
@ -288,7 +342,7 @@ const onClose = () => {
<n-form-item :label="$t('dialogue.connection.addr')" required> <n-form-item :label="$t('dialogue.connection.addr')" required>
<n-input <n-input
v-model:value="generalForm.ssh.addr" v-model:value="generalForm.ssh.addr"
:placeholder="$t('dialogue.connection.ssh_addr_tip')" /> :placeholder="$t('dialogue.connection.ssh.addr_tip')" />
<n-text style="width: 40px; text-align: center">:</n-text> <n-text style="width: 40px; text-align: center">:</n-text>
<n-input-number <n-input-number
v-model:value="generalForm.ssh.port" v-model:value="generalForm.ssh.port"
@ -296,10 +350,10 @@ const onClose = () => {
:min="1" :min="1"
style="width: 200px" /> style="width: 200px" />
</n-form-item> </n-form-item>
<n-form-item :label="$t('dialogue.connection.login_type')"> <n-form-item :label="$t('dialogue.connection.ssh.login_type')">
<n-radio-group v-model:value="generalForm.ssh.loginType"> <n-radio-group v-model:value="generalForm.ssh.loginType">
<n-radio-button :label="$t('dialogue.connection.pwd')" value="pwd" /> <n-radio-button :label="$t('dialogue.connection.pwd')" value="pwd" />
<n-radio-button :label="$t('dialogue.connection.pkfile')" value="pkfile" /> <n-radio-button :label="$t('dialogue.connection.ssh.pkfile')" value="pkfile" />
</n-radio-group> </n-radio-group>
</n-form-item> </n-form-item>
<n-form-item <n-form-item
@ -307,27 +361,27 @@ const onClose = () => {
:label="$t('dialogue.connection.usr')"> :label="$t('dialogue.connection.usr')">
<n-input <n-input
v-model:value="generalForm.ssh.username" v-model:value="generalForm.ssh.username"
:placeholder="$t('dialogue.connection.ssh_usr_tip')" /> :placeholder="$t('dialogue.connection.ssh.usr_tip')" />
</n-form-item> </n-form-item>
<n-form-item v-if="sshLoginType === 'pwd'" :label="$t('dialogue.connection.pwd')"> <n-form-item v-if="sshLoginType === 'pwd'" :label="$t('dialogue.connection.pwd')">
<n-input <n-input
v-model:value="generalForm.ssh.password" v-model:value="generalForm.ssh.password"
:placeholder="$t('dialogue.connection.ssh_pwd_tip')" :placeholder="$t('dialogue.connection.ssh.pwd_tip')"
show-password-on="click" show-password-on="click"
type="password" /> type="password" />
</n-form-item> </n-form-item>
<n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.pkfile')"> <n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.ssh.pkfile')">
<n-input-group> <n-input-group>
<n-input <n-input
v-model:value="generalForm.ssh.pkFile" v-model:value="generalForm.ssh.pkFile"
:placeholder="$t('dialogue.connection.pkfile_tip')" /> :placeholder="$t('dialogue.connection.ssh.pkfile_tip')" />
<n-button :focusable="false" @click="onChoosePKFile">...</n-button> <n-button :focusable="false" @click="onChoosePKFile">...</n-button>
</n-input-group> </n-input-group>
</n-form-item> </n-form-item>
<n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.passphrase')"> <n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.ssh.passphrase')">
<n-input <n-input
v-model:value="generalForm.ssh.passphrase" v-model:value="generalForm.ssh.passphrase"
:placeholder="$t('dialogue.connection.passphrase_tip')" :placeholder="$t('dialogue.connection.ssh.passphrase_tip')"
show-password-on="click" show-password-on="click"
type="password" /> type="password" />
</n-form-item> </n-form-item>

View File

@ -127,24 +127,36 @@
"test": "Test Connection", "test": "Test Connection",
"test_succ": "Successful connection to redis-server", "test_succ": "Successful connection to redis-server",
"test_fail": "Fail Connection", "test_fail": "Fail Connection",
"advn_filter": "Default Filter", "advn": {
"advn_filter_tip": "Pattern which defines loaded keys from redis server", "filter": "Default Key Filter Pattern",
"advn_separator": "Key Separator", "filter_tip": "Pattern which defines loaded keys from redis server",
"advn_separator_tip": "Separator used for key path item", "separator": "Key Separator",
"advn_conn_timeout": "Connection Timeout", "separator_tip":"Separator for key path item",
"advn_exec_timeout": "Execution Timeout", "conn_timeout": "Connection Timeout",
"advn_mark_color": "Mark Color", "exec_timeout": "Execution Timeout",
"ssh_enable": "Enable SSH Tuntel", "dbfilter_type": "Database Filter",
"ssh_tunnel": "SSH Tunnel", "dbfilter_all": "Show All",
"login_type": "Login Type", "dbfilter_show": "Show Selected",
"pkfile": "Private Key File", "dbfilter_hide": "Hide Selected",
"passphrase": "Passphrase", "dbfilter_show_title": "Select the Databases to Show",
"ssh_addr_tip": "SSH Server Host", "dbfilter_hide_title": "Select the Databases to Hide",
"ssh_usr_tip": "SSH Username", "dbfilter_input": "Input Database Index",
"ssh_pwd_tip": "SSH Password", "dbfilter_input_tip": "Press Enter to confirm",
"pkfile_tip": "SSH Private Key File Path", "mark_color": "Mark Color"
"passphrase_tip": "(Optional) Passphrase for Private Key", },
"pkfile_selection_title": "Please Select Private Key File" "ssh": {
"enable": "Enable SSH Tuntel",
"tunnel": "SSH Tunnel",
"login_type": "Login Type",
"pkfile": "Private Key File",
"passphrase": "Passphrase",
"addr_tip": "SSH Server Host",
"usr_tip": "SSH Username",
"pwd_tip": "SSH Password",
"pkfile_tip": "SSH Private Key File Path",
"passphrase_tip": "(Optional) Passphrase for Private Key",
"pkfile_selection_title": "Please Select Private Key File"
}
}, },
"group": { "group": {
"name": "Group Name", "name": "Group Name",

View File

@ -127,24 +127,36 @@
"test": "测试连接", "test": "测试连接",
"test_succ": "成功连接到Redis服务器", "test_succ": "成功连接到Redis服务器",
"test_fail": "连接失败", "test_fail": "连接失败",
"advn_filter": "默认过滤", "advn": {
"advn_filter_tip": "需要加载的键名表达式", "filter": "默认键过滤表达式",
"advn_separator": "键分隔符", "filter_tip": "需要加载的键名表达式",
"advn_separator_tip": "键名路径分隔符", "separator": "键分隔符",
"advn_conn_timeout": "连接超时", "separator_tip":"键名路径分隔符",
"advn_exec_timeout": "执行超时", "conn_timeout": "连接超时",
"advn_mark_color": "标记颜色", "exec_timeout": "执行超时",
"ssh_enable": "启用SSH隧道", "dbfilter_type": "数据库过滤方式",
"ssh_tunnel": "SSH隧道", "dbfilter_all": "显示所有",
"login_type": "登录类型", "dbfilter_show": "显示指定",
"pkfile": "私钥文件", "dbfilter_hide": "隐藏指定",
"passphrase": "私钥密码", "dbfilter_show_title": "需要显示的数据库",
"ssh_addr_tip": "SSH地址", "dbfilter_hide_title": "需要隐藏的数据库",
"ssh_usr_tip": "SSH登录用户名", "dbfilter_input": "输入数据库索引",
"ssh_pwd_tip": "SSH登录密码", "dbfilter_input_tip": "按回车确认",
"pkfile_tip": "SSH私钥文件路径", "mark_color": "标记颜色"
"passphrase_tip": "(可选)SSH私钥密码", },
"pkfile_selection_title": "请选择私钥文件" "ssh": {
"enable": "启用SSH隧道",
"tunnel": "SSH隧道",
"login_type": "登录类型",
"pkfile": "私钥文件",
"passphrase": "私钥密码",
"addr_tip": "SSH地址",
"usr_tip": "SSH登录用户名",
"pwd_tip": "SSH登录密码",
"pkfile_tip": "SSH私钥文件路径",
"passphrase_tip": "(可选)SSH私钥密码",
"pkfile_selection_title": "请选择私钥文件"
}
}, },
"group": { "group": {
"name": "分组名", "name": "分组名",

View File

@ -208,6 +208,8 @@ const useConnectionStore = defineStore('connections', {
keySeparator: ':', keySeparator: ':',
connTimeout: 60, connTimeout: 60,
execTimeout: 60, execTimeout: 60,
dbFilterType: 'none',
dbFilterList: [],
markColor: '', markColor: '',
ssh: { ssh: {
enable: false, enable: false,

View File

@ -1,5 +1,6 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import useConnectionStore from './connections.js' import useConnectionStore from './connections.js'
import { assignWith, isEmpty } from 'lodash'
/** /**
* connection dialog type * connection dialog type
@ -81,7 +82,16 @@ const useDialogStore = defineStore('dialog', {
async openEditDialog(name) { async openEditDialog(name) {
const connStore = useConnectionStore() const connStore = useConnectionStore()
const profile = await connStore.getConnectionProfile(name) const profile = await connStore.getConnectionProfile(name)
this.connParam = profile || connStore.newDefaultConnection(name) const assignCustomizer = (objVal, srcVal, key) => {
if (isEmpty(objVal)) {
return srcVal
}
if (isEmpty(srcVal)) {
return objVal
}
return undefined
}
this.connParam = assignWith({}, connStore.newDefaultConnection(name), profile, assignCustomizer)
this.connType = ConnDialogType.EDIT this.connType = ConnDialogType.EDIT
this.connDialogVisible = true this.connDialogVisible = true
}, },