feat: add discovery master group name for sentinel mode config
perf: tidy connection profile file
This commit is contained in:
parent
ee68d699fa
commit
b5dfe377fa
|
@ -74,7 +74,7 @@ func (c *connectionService) Stop(ctx context.Context) {
|
|||
c.connMap = map[string]connectionItem{}
|
||||
}
|
||||
|
||||
func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*redis.Client, error) {
|
||||
func (c *connectionService) buildOption(config types.ConnectionConfig) (*redis.Options, error) {
|
||||
var sshClient *ssh.Client
|
||||
if config.SSH.Enable {
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
|
@ -127,10 +127,21 @@ func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*r
|
|||
option.ReadTimeout = -2
|
||||
option.WriteTimeout = -2
|
||||
}
|
||||
return option, nil
|
||||
}
|
||||
|
||||
func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*redis.Client, error) {
|
||||
option, err := c.buildOption(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.Sentinel.Enable {
|
||||
sentinel := redis.NewSentinelClient(option)
|
||||
addr, err := sentinel.GetMasterAddrByName(c.ctx, config.Sentinel.Master).Result()
|
||||
defer sentinel.Close()
|
||||
|
||||
var addr []string
|
||||
addr, err = sentinel.GetMasterAddrByName(c.ctx, config.Sentinel.Master).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -146,6 +157,40 @@ func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*r
|
|||
return rdb, nil
|
||||
}
|
||||
|
||||
// ListSentinelMasters list all master info by sentinel
|
||||
func (c *connectionService) ListSentinelMasters(config types.ConnectionConfig) (resp types.JSResp) {
|
||||
option, err := c.buildOption(config)
|
||||
if err != nil {
|
||||
resp.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
if option.DialTimeout > 0 {
|
||||
option.DialTimeout = 10 * time.Second
|
||||
}
|
||||
sentinel := redis.NewSentinelClient(option)
|
||||
defer sentinel.Close()
|
||||
|
||||
var retInfo []map[string]string
|
||||
masterInfos, err := sentinel.Masters(c.ctx).Result()
|
||||
if err != nil {
|
||||
resp.Msg = err.Error()
|
||||
return
|
||||
}
|
||||
for _, info := range masterInfos {
|
||||
if infoMap, ok := info.(map[any]any); ok {
|
||||
retInfo = append(retInfo, map[string]string{
|
||||
"name": infoMap["name"].(string),
|
||||
"addr": fmt.Sprintf("%s:%s", infoMap["ip"].(string), infoMap["port"].(string)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resp.Data = retInfo
|
||||
resp.Success = true
|
||||
return
|
||||
}
|
||||
|
||||
func (c *connectionService) TestConnection(config types.ConnectionConfig) (resp types.JSResp) {
|
||||
rdb, err := c.createRedisClient(config)
|
||||
if err != nil {
|
||||
|
@ -153,7 +198,8 @@ func (c *connectionService) TestConnection(config types.ConnectionConfig) (resp
|
|||
return
|
||||
}
|
||||
defer rdb.Close()
|
||||
if _, err := rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
|
||||
|
||||
if _, err = rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
|
||||
resp.Msg = err.Error()
|
||||
} else {
|
||||
resp.Success = true
|
||||
|
|
|
@ -195,8 +195,7 @@ func (c *ConnectionsStorage) UpdateConnection(name string, param types.Connectio
|
|||
updated = true
|
||||
}
|
||||
} else {
|
||||
err := retrieve(conn.Connections, name, param)
|
||||
if err != nil {
|
||||
if err := retrieve(conn.Connections, name, param); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +286,7 @@ func (c *ConnectionsStorage) SaveSortedConnection(sortedConns types.Connections)
|
|||
return c.saveConnections(conns)
|
||||
}
|
||||
|
||||
// CreateGroup create new group
|
||||
// CreateGroup create a new group
|
||||
func (c *ConnectionsStorage) CreateGroup(name string) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
@ -333,7 +332,7 @@ func (c *ConnectionsStorage) RenameGroup(name, newName string) error {
|
|||
return c.saveConnections(conns)
|
||||
}
|
||||
|
||||
// DeleteGroup remove special group, include all connections under it
|
||||
// DeleteGroup remove specified group, include all connections under it
|
||||
func (c *ConnectionsStorage) DeleteGroup(group string, includeConnection bool) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
|
|
@ -45,7 +45,7 @@ type ConnectionSSH struct {
|
|||
Addr string `json:"addr,omitempty" yaml:"addr,omitempty"`
|
||||
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
||||
LoginType string `json:"loginType" yaml:"login_type"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Username string `json:"username" yaml:"username,omitempty"`
|
||||
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
||||
PKFile string `json:"pkFile,omitempty" yaml:"pk_file,omitempty"`
|
||||
Passphrase string `json:"passphrase,omitempty" yaml:"passphrase,omitempty"`
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { every, get, includes, isEmpty, map, sortBy, toNumber } from 'lodash'
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { SelectKeyFile, TestConnection } from 'wailsjs/go/services/connectionService.js'
|
||||
import { ListSentinelMasters, SelectKeyFile, TestConnection } from 'wailsjs/go/services/connectionService.js'
|
||||
import useDialog, { ConnDialogType } from 'stores/dialog'
|
||||
import Close from '@/components/icons/Close.vue'
|
||||
import useConnectionStore from 'stores/connections.js'
|
||||
|
@ -57,17 +57,27 @@ const groupOptions = computed(() => {
|
|||
})
|
||||
|
||||
const dbFilterList = ref([])
|
||||
const onUpdateDBFilterList = (list) => {
|
||||
const dbList = []
|
||||
for (const item of list) {
|
||||
const idx = toNumber(item)
|
||||
if (!isNaN(idx)) {
|
||||
dbList.push(idx)
|
||||
const onUpdateDBFilterType = (t) => {
|
||||
if (t !== 'none') {
|
||||
// set default filter index if empty
|
||||
if (isEmpty(dbFilterList.value)) {
|
||||
dbFilterList.value = ['0']
|
||||
}
|
||||
}
|
||||
generalForm.value.dbFilterList = sortBy(dbList)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dbFilterList.value,
|
||||
(list) => {
|
||||
const dbList = map(list, (item) => {
|
||||
const idx = toNumber(item)
|
||||
return isNaN(idx) ? 0 : idx
|
||||
})
|
||||
generalForm.value.dbFilterList = sortBy(dbList)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
const sshLoginType = computed(() => {
|
||||
return get(generalForm.value, 'ssh.loginType', 'pwd')
|
||||
})
|
||||
|
@ -81,6 +91,36 @@ const onChoosePKFile = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
const loadingSentinelMaster = ref(false)
|
||||
const masterNameOptions = ref([])
|
||||
const onLoadSentinelMasters = async () => {
|
||||
try {
|
||||
loadingSentinelMaster.value = true
|
||||
const { success, data, msg } = await ListSentinelMasters(generalForm.value)
|
||||
if (!success || isEmpty(data)) {
|
||||
$message.error(msg || 'list sentinel master fail')
|
||||
} else {
|
||||
const options = []
|
||||
for (const m of data) {
|
||||
options.push({
|
||||
label: m['name'],
|
||||
value: m['name'],
|
||||
})
|
||||
}
|
||||
|
||||
// select default names
|
||||
if (!isEmpty(options)) {
|
||||
generalForm.value.sentinel.master = options[0].value
|
||||
}
|
||||
masterNameOptions.value = options
|
||||
}
|
||||
} catch (e) {
|
||||
$message.error(e.message)
|
||||
} finally {
|
||||
loadingSentinelMaster.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const tab = ref('general')
|
||||
const testing = ref(false)
|
||||
const showTestResult = ref(false)
|
||||
|
@ -104,6 +144,11 @@ const onSaveConnection = async () => {
|
|||
}
|
||||
})
|
||||
|
||||
// trim advance data
|
||||
if (get(generalForm.value, 'dbFilterType', 'none') === 'none') {
|
||||
generalForm.value.dbFilterList = []
|
||||
}
|
||||
|
||||
// trim ssh login data
|
||||
if (generalForm.value.ssh.enable) {
|
||||
switch (generalForm.value.ssh.loginType) {
|
||||
|
@ -121,6 +166,13 @@ const onSaveConnection = async () => {
|
|||
generalForm.value.ssh = ssh
|
||||
}
|
||||
|
||||
// trim sentinel data
|
||||
if (!generalForm.value.sentinel.enable) {
|
||||
generalForm.value.sentinel.master = ''
|
||||
generalForm.value.sentinel.username = ''
|
||||
generalForm.value.sentinel.password = ''
|
||||
}
|
||||
|
||||
// store new connection
|
||||
const { success, msg } = await connectionStore.saveConnection(
|
||||
isEditMode.value ? editName.value : null,
|
||||
|
@ -142,6 +194,7 @@ const resetForm = () => {
|
|||
showTestResult.value = false
|
||||
testResult.value = ''
|
||||
tab.value = 'general'
|
||||
loadingSentinelMaster.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@ -286,7 +339,9 @@ const onClose = () => {
|
|||
</n-input-number>
|
||||
</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-group
|
||||
v-model:value="generalForm.dbFilterType"
|
||||
@update:value="onUpdateDBFilterType">
|
||||
<n-radio-button :label="$t('dialogue.connection.advn.dbfilter_all')" value="none" />
|
||||
<n-radio-button
|
||||
:label="$t('dialogue.connection.advn.dbfilter_show')"
|
||||
|
@ -296,7 +351,10 @@ const onClose = () => {
|
|||
value="hide" />
|
||||
</n-radio-group>
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi :span="24" :label="$t('dialogue.connection.advn.dbfilter_input')">
|
||||
<n-form-item-gi
|
||||
v-show="generalForm.dbFilterType !== 'none'"
|
||||
:span="24"
|
||||
:label="$t('dialogue.connection.advn.dbfilter_input')">
|
||||
<n-select
|
||||
v-model:value="dbFilterList"
|
||||
:disabled="generalForm.dbFilterType === 'none'"
|
||||
|
@ -306,8 +364,7 @@ const onClose = () => {
|
|||
:placeholder="$t('dialogue.connection.advn.dbfilter_input_tip')"
|
||||
:show-arrow="false"
|
||||
:show="false"
|
||||
:clearable="true"
|
||||
@update:value="onUpdateDBFilterList" />
|
||||
:clearable="true" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi
|
||||
:span="24"
|
||||
|
@ -403,9 +460,16 @@ const onClose = () => {
|
|||
:disabled="!generalForm.sentinel.enable"
|
||||
label-placement="top">
|
||||
<n-form-item :label="$t('dialogue.connection.sentinel.master')">
|
||||
<n-input
|
||||
v-model:value="generalForm.sentinel.master"
|
||||
:placeholder="$t('dialogue.connection.sentinel.master')" />
|
||||
<n-space>
|
||||
<n-select
|
||||
v-model:value="generalForm.sentinel.master"
|
||||
filterable
|
||||
tag
|
||||
:options="masterNameOptions" />
|
||||
<n-button :loading="loadingSentinelMaster" @click="onLoadSentinelMasters">
|
||||
{{ $t('dialogue.connection.sentinel.auto_discover') }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$t('dialogue.connection.sentinel.password')">
|
||||
<n-input
|
||||
|
@ -423,7 +487,6 @@ const onClose = () => {
|
|||
</n-tab-pane>
|
||||
|
||||
<!-- TODO: SSL tab pane -->
|
||||
<!-- TODO: Sentinel tab pane -->
|
||||
<!-- TODO: Cluster tab pane -->
|
||||
</n-tabs>
|
||||
|
||||
|
|
|
@ -122,8 +122,8 @@
|
|||
"pwd": "Password",
|
||||
"name_tip": "Connection name",
|
||||
"addr_tip": "Redis server host",
|
||||
"usr_tip": "(Optional) Redis server username",
|
||||
"pwd_tip": "(Optional) Redis server authentication password (Redis > 6.0)",
|
||||
"usr_tip": "(Optional) Authentication username",
|
||||
"pwd_tip": "(Optional) Authentication password (Redis > 6.0)",
|
||||
"test": "Test Connection",
|
||||
"test_succ": "Successful connection to redis-server",
|
||||
"test_fail": "Fail Connection",
|
||||
|
@ -161,11 +161,12 @@
|
|||
"sentinel": {
|
||||
"title": "Sentinel",
|
||||
"enable": "Serve as Sentinel Node",
|
||||
"master": "Name of Master Node",
|
||||
"master": "Master Group Name",
|
||||
"auto_discover": "Auto Discover",
|
||||
"password": "Password for Master Node",
|
||||
"username": "Username for Master Node",
|
||||
"pwd_tip": "(Optional) username for master node",
|
||||
"usr_tip": "(Optional) authentication password for master node (Redis > 6.0)"
|
||||
"pwd_tip": "(Optional) Authentication username for master node",
|
||||
"usr_tip": "(Optional) Authentication password for master node (Redis > 6.0)"
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
"filter": "默认键过滤表达式",
|
||||
"filter_tip": "需要加载的键名表达式",
|
||||
"separator": "键分隔符",
|
||||
"separator_tip":"键名路径分隔符",
|
||||
"separator_tip": "键名路径分隔符",
|
||||
"conn_timeout": "连接超时",
|
||||
"exec_timeout": "执行超时",
|
||||
"dbfilter_type": "数据库过滤方式",
|
||||
|
@ -161,7 +161,8 @@
|
|||
"sentinel": {
|
||||
"title": "哨兵模式",
|
||||
"enable": "当前为哨兵节点",
|
||||
"master": "主实例名",
|
||||
"master": "主节点组名",
|
||||
"auto_discover": "自动查询组名",
|
||||
"password": "主节点密码",
|
||||
"username": "主节点用户名",
|
||||
"pwd_tip": "(可选)主节点服务授权用户名",
|
||||
|
@ -209,7 +210,7 @@
|
|||
"ttl": {
|
||||
"title": "设置键存活时间"
|
||||
},
|
||||
"upgrade":{
|
||||
"upgrade": {
|
||||
"title": "有可用新版本",
|
||||
"new_version_tip": "新版本({ver}),是否立即下载",
|
||||
"no_update": "当前已是最新版",
|
||||
|
|
|
@ -230,6 +230,25 @@ const useConnectionStore = defineStore('connections', {
|
|||
}
|
||||
},
|
||||
|
||||
mergeConnectionProfile(dest, src) {
|
||||
const mergeObj = (destObj, srcObj) => {
|
||||
for (const k in srcObj) {
|
||||
const t = typeof srcObj[k]
|
||||
if (t === 'string') {
|
||||
destObj[k] = srcObj[k] || destObj[k] || ''
|
||||
} else if (t === 'number') {
|
||||
destObj[k] = srcObj[k] || destObj[k] || 0
|
||||
} else if (t === 'object') {
|
||||
mergeObj(destObj[k], srcObj[k] || {})
|
||||
} else {
|
||||
destObj[k] = srcObj[k]
|
||||
}
|
||||
}
|
||||
return destObj
|
||||
}
|
||||
return mergeObj(dest, src)
|
||||
},
|
||||
|
||||
/**
|
||||
* get database server by name
|
||||
* @param name
|
||||
|
|
|
@ -82,16 +82,7 @@ const useDialogStore = defineStore('dialog', {
|
|||
async openEditDialog(name) {
|
||||
const connStore = useConnectionStore()
|
||||
const profile = await connStore.getConnectionProfile(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.connParam = connStore.mergeConnectionProfile(connStore.newDefaultConnection(name), profile)
|
||||
this.connType = ConnDialogType.EDIT
|
||||
this.connDialogVisible = true
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue