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{}
|
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
|
var sshClient *ssh.Client
|
||||||
if config.SSH.Enable {
|
if config.SSH.Enable {
|
||||||
sshConfig := &ssh.ClientConfig{
|
sshConfig := &ssh.ClientConfig{
|
||||||
|
@ -127,10 +127,21 @@ func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*r
|
||||||
option.ReadTimeout = -2
|
option.ReadTimeout = -2
|
||||||
option.WriteTimeout = -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 {
|
if config.Sentinel.Enable {
|
||||||
sentinel := redis.NewSentinelClient(option)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -146,6 +157,40 @@ func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*r
|
||||||
return rdb, nil
|
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) {
|
func (c *connectionService) TestConnection(config types.ConnectionConfig) (resp types.JSResp) {
|
||||||
rdb, err := c.createRedisClient(config)
|
rdb, err := c.createRedisClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -153,7 +198,8 @@ func (c *connectionService) TestConnection(config types.ConnectionConfig) (resp
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rdb.Close()
|
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()
|
resp.Msg = err.Error()
|
||||||
} else {
|
} else {
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
|
|
|
@ -195,8 +195,7 @@ func (c *ConnectionsStorage) UpdateConnection(name string, param types.Connectio
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := retrieve(conn.Connections, name, param)
|
if err := retrieve(conn.Connections, name, param); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,7 +286,7 @@ func (c *ConnectionsStorage) SaveSortedConnection(sortedConns types.Connections)
|
||||||
return c.saveConnections(conns)
|
return c.saveConnections(conns)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateGroup create new group
|
// CreateGroup create a new group
|
||||||
func (c *ConnectionsStorage) CreateGroup(name string) error {
|
func (c *ConnectionsStorage) CreateGroup(name string) error {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
@ -333,7 +332,7 @@ func (c *ConnectionsStorage) RenameGroup(name, newName string) error {
|
||||||
return c.saveConnections(conns)
|
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 {
|
func (c *ConnectionsStorage) DeleteGroup(group string, includeConnection bool) error {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
|
@ -45,7 +45,7 @@ type ConnectionSSH struct {
|
||||||
Addr string `json:"addr,omitempty" yaml:"addr,omitempty"`
|
Addr string `json:"addr,omitempty" yaml:"addr,omitempty"`
|
||||||
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
||||||
LoginType string `json:"loginType" yaml:"login_type"`
|
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"`
|
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
||||||
PKFile string `json:"pkFile,omitempty" yaml:"pk_file,omitempty"`
|
PKFile string `json:"pkFile,omitempty" yaml:"pk_file,omitempty"`
|
||||||
Passphrase string `json:"passphrase,omitempty" yaml:"passphrase,omitempty"`
|
Passphrase string `json:"passphrase,omitempty" yaml:"passphrase,omitempty"`
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { every, get, includes, isEmpty, map, sortBy, toNumber } 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 { ListSentinelMasters, SelectKeyFile, TestConnection } from 'wailsjs/go/services/connectionService.js'
|
||||||
import useDialog, { ConnDialogType } from 'stores/dialog'
|
import useDialog, { ConnDialogType } from 'stores/dialog'
|
||||||
import Close from '@/components/icons/Close.vue'
|
import Close from '@/components/icons/Close.vue'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
import useConnectionStore from 'stores/connections.js'
|
||||||
|
@ -57,16 +57,26 @@ const groupOptions = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const dbFilterList = ref([])
|
const dbFilterList = ref([])
|
||||||
const onUpdateDBFilterList = (list) => {
|
const onUpdateDBFilterType = (t) => {
|
||||||
const dbList = []
|
if (t !== 'none') {
|
||||||
for (const item of list) {
|
// set default filter index if empty
|
||||||
|
if (isEmpty(dbFilterList.value)) {
|
||||||
|
dbFilterList.value = ['0']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => dbFilterList.value,
|
||||||
|
(list) => {
|
||||||
|
const dbList = map(list, (item) => {
|
||||||
const idx = toNumber(item)
|
const idx = toNumber(item)
|
||||||
if (!isNaN(idx)) {
|
return isNaN(idx) ? 0 : idx
|
||||||
dbList.push(idx)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
generalForm.value.dbFilterList = sortBy(dbList)
|
generalForm.value.dbFilterList = sortBy(dbList)
|
||||||
}
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
const sshLoginType = computed(() => {
|
const sshLoginType = computed(() => {
|
||||||
return get(generalForm.value, 'ssh.loginType', 'pwd')
|
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 tab = ref('general')
|
||||||
const testing = ref(false)
|
const testing = ref(false)
|
||||||
const showTestResult = 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
|
// trim ssh login data
|
||||||
if (generalForm.value.ssh.enable) {
|
if (generalForm.value.ssh.enable) {
|
||||||
switch (generalForm.value.ssh.loginType) {
|
switch (generalForm.value.ssh.loginType) {
|
||||||
|
@ -121,6 +166,13 @@ const onSaveConnection = async () => {
|
||||||
generalForm.value.ssh = ssh
|
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
|
// store new connection
|
||||||
const { success, msg } = await connectionStore.saveConnection(
|
const { success, msg } = await connectionStore.saveConnection(
|
||||||
isEditMode.value ? editName.value : null,
|
isEditMode.value ? editName.value : null,
|
||||||
|
@ -142,6 +194,7 @@ const resetForm = () => {
|
||||||
showTestResult.value = false
|
showTestResult.value = false
|
||||||
testResult.value = ''
|
testResult.value = ''
|
||||||
tab.value = 'general'
|
tab.value = 'general'
|
||||||
|
loadingSentinelMaster.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -286,7 +339,9 @@ const onClose = () => {
|
||||||
</n-input-number>
|
</n-input-number>
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="24" :label="$t('dialogue.connection.advn.dbfilter_type')">
|
<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_all')" value="none" />
|
||||||
<n-radio-button
|
<n-radio-button
|
||||||
:label="$t('dialogue.connection.advn.dbfilter_show')"
|
:label="$t('dialogue.connection.advn.dbfilter_show')"
|
||||||
|
@ -296,7 +351,10 @@ const onClose = () => {
|
||||||
value="hide" />
|
value="hide" />
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</n-form-item-gi>
|
</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
|
<n-select
|
||||||
v-model:value="dbFilterList"
|
v-model:value="dbFilterList"
|
||||||
:disabled="generalForm.dbFilterType === 'none'"
|
:disabled="generalForm.dbFilterType === 'none'"
|
||||||
|
@ -306,8 +364,7 @@ const onClose = () => {
|
||||||
:placeholder="$t('dialogue.connection.advn.dbfilter_input_tip')"
|
:placeholder="$t('dialogue.connection.advn.dbfilter_input_tip')"
|
||||||
:show-arrow="false"
|
:show-arrow="false"
|
||||||
:show="false"
|
:show="false"
|
||||||
:clearable="true"
|
:clearable="true" />
|
||||||
@update:value="onUpdateDBFilterList" />
|
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi
|
<n-form-item-gi
|
||||||
:span="24"
|
:span="24"
|
||||||
|
@ -403,9 +460,16 @@ const onClose = () => {
|
||||||
:disabled="!generalForm.sentinel.enable"
|
:disabled="!generalForm.sentinel.enable"
|
||||||
label-placement="top">
|
label-placement="top">
|
||||||
<n-form-item :label="$t('dialogue.connection.sentinel.master')">
|
<n-form-item :label="$t('dialogue.connection.sentinel.master')">
|
||||||
<n-input
|
<n-space>
|
||||||
|
<n-select
|
||||||
v-model:value="generalForm.sentinel.master"
|
v-model:value="generalForm.sentinel.master"
|
||||||
:placeholder="$t('dialogue.connection.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>
|
||||||
<n-form-item :label="$t('dialogue.connection.sentinel.password')">
|
<n-form-item :label="$t('dialogue.connection.sentinel.password')">
|
||||||
<n-input
|
<n-input
|
||||||
|
@ -423,7 +487,6 @@ const onClose = () => {
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- TODO: SSL tab pane -->
|
<!-- TODO: SSL tab pane -->
|
||||||
<!-- TODO: Sentinel tab pane -->
|
|
||||||
<!-- TODO: Cluster tab pane -->
|
<!-- TODO: Cluster tab pane -->
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
|
|
||||||
|
|
|
@ -122,8 +122,8 @@
|
||||||
"pwd": "Password",
|
"pwd": "Password",
|
||||||
"name_tip": "Connection name",
|
"name_tip": "Connection name",
|
||||||
"addr_tip": "Redis server host",
|
"addr_tip": "Redis server host",
|
||||||
"usr_tip": "(Optional) Redis server username",
|
"usr_tip": "(Optional) Authentication username",
|
||||||
"pwd_tip": "(Optional) Redis server authentication password (Redis > 6.0)",
|
"pwd_tip": "(Optional) Authentication password (Redis > 6.0)",
|
||||||
"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",
|
||||||
|
@ -161,11 +161,12 @@
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"title": "Sentinel",
|
"title": "Sentinel",
|
||||||
"enable": "Serve as Sentinel Node",
|
"enable": "Serve as Sentinel Node",
|
||||||
"master": "Name of Master Node",
|
"master": "Master Group Name",
|
||||||
|
"auto_discover": "Auto Discover",
|
||||||
"password": "Password for Master Node",
|
"password": "Password for Master Node",
|
||||||
"username": "Username for Master Node",
|
"username": "Username for Master Node",
|
||||||
"pwd_tip": "(Optional) username for master node",
|
"pwd_tip": "(Optional) Authentication username for master node",
|
||||||
"usr_tip": "(Optional) authentication password for master node (Redis > 6.0)"
|
"usr_tip": "(Optional) Authentication password for master node (Redis > 6.0)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
|
|
|
@ -161,7 +161,8 @@
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"title": "哨兵模式",
|
"title": "哨兵模式",
|
||||||
"enable": "当前为哨兵节点",
|
"enable": "当前为哨兵节点",
|
||||||
"master": "主实例名",
|
"master": "主节点组名",
|
||||||
|
"auto_discover": "自动查询组名",
|
||||||
"password": "主节点密码",
|
"password": "主节点密码",
|
||||||
"username": "主节点用户名",
|
"username": "主节点用户名",
|
||||||
"pwd_tip": "(可选)主节点服务授权用户名",
|
"pwd_tip": "(可选)主节点服务授权用户名",
|
||||||
|
|
|
@ -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
|
* get database server by name
|
||||||
* @param name
|
* @param name
|
||||||
|
|
|
@ -82,16 +82,7 @@ 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)
|
||||||
const assignCustomizer = (objVal, srcVal, key) => {
|
this.connParam = connStore.mergeConnectionProfile(connStore.newDefaultConnection(name), profile)
|
||||||
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
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue