parent
ebacf2bd57
commit
47df424138
|
@ -5,6 +5,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -68,12 +72,69 @@ func (c *connectionService) Stop(ctx context.Context) {
|
||||||
c.connMap = map[string]connectionItem{}
|
c.connMap = map[string]connectionItem{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *connectionService) TestConnection(host string, port int, username, password string) (resp types.JSResp) {
|
func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*redis.Client, error) {
|
||||||
rdb := redis.NewClient(&redis.Options{
|
var sshClient *ssh.Client
|
||||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
if config.SSH.Enable {
|
||||||
Username: username,
|
sshConfig := &ssh.ClientConfig{
|
||||||
Password: password,
|
User: config.SSH.Username,
|
||||||
})
|
Auth: []ssh.AuthMethod{ssh.Password(config.SSH.Password)},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
Timeout: time.Duration(config.ConnTimeout) * time.Second,
|
||||||
|
}
|
||||||
|
switch config.SSH.LoginType {
|
||||||
|
case "pwd":
|
||||||
|
sshConfig.Auth = []ssh.AuthMethod{ssh.Password(config.SSH.Password)}
|
||||||
|
case "pkfile":
|
||||||
|
key, err := os.ReadFile(config.SSH.PKFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var signer ssh.Signer
|
||||||
|
if len(config.SSH.Passphrase) > 0 {
|
||||||
|
signer, err = ssh.ParsePrivateKeyWithPassphrase(key, []byte(config.SSH.Passphrase))
|
||||||
|
} else {
|
||||||
|
signer, err = ssh.ParsePrivateKey(key)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sshConfig.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid login type")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
sshClient, err = ssh.Dial("tcp", fmt.Sprintf("%s:%d", config.SSH.Addr, config.SSH.Port), sshConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
option := &redis.Options{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", config.Addr, config.Port),
|
||||||
|
Username: config.Username,
|
||||||
|
Password: config.Password,
|
||||||
|
DialTimeout: time.Duration(config.ConnTimeout) * time.Second,
|
||||||
|
ReadTimeout: time.Duration(config.ExecTimeout) * time.Second,
|
||||||
|
WriteTimeout: time.Duration(config.ExecTimeout) * time.Second,
|
||||||
|
}
|
||||||
|
if sshClient != nil {
|
||||||
|
option.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return sshClient.Dial(network, addr)
|
||||||
|
}
|
||||||
|
option.ReadTimeout = -2
|
||||||
|
option.WriteTimeout = -2
|
||||||
|
}
|
||||||
|
rdb := redis.NewClient(option)
|
||||||
|
return rdb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connectionService) TestConnection(config types.ConnectionConfig) (resp types.JSResp) {
|
||||||
|
rdb, err := c.createRedisClient(config)
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
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()
|
||||||
|
@ -141,6 +202,23 @@ func (c *connectionService) SaveSortedConnection(sortedConns types.Connections)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectKeyFile open file dialog to select a private key file
|
||||||
|
func (c *connectionService) SelectKeyFile(title string) (resp types.JSResp) {
|
||||||
|
filepath, err := runtime.OpenFileDialog(c.ctx, runtime.OpenDialogOptions{
|
||||||
|
Title: title,
|
||||||
|
ShowHiddenFiles: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Success = true
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"path": filepath,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// CreateGroup create a new group
|
// CreateGroup create a new group
|
||||||
func (c *connectionService) CreateGroup(name string) (resp types.JSResp) {
|
func (c *connectionService) CreateGroup(name string) (resp types.JSResp) {
|
||||||
err := c.conns.CreateGroup(name)
|
err := c.conns.CreateGroup(name)
|
||||||
|
@ -252,14 +330,11 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
|
||||||
return nil, nil, fmt.Errorf("no match connection \"%s\"", connName)
|
return nil, nil, fmt.Errorf("no match connection \"%s\"", connName)
|
||||||
}
|
}
|
||||||
|
|
||||||
rdb = redis.NewClient(&redis.Options{
|
var err error
|
||||||
Addr: fmt.Sprintf("%s:%d", selConn.Addr, selConn.Port),
|
rdb, err = c.createRedisClient(selConn.ConnectionConfig)
|
||||||
Username: selConn.Username,
|
if err != nil {
|
||||||
Password: selConn.Password,
|
return nil, nil, fmt.Errorf("create conenction error: %s", err.Error())
|
||||||
DialTimeout: time.Duration(selConn.ConnTimeout) * time.Second,
|
}
|
||||||
ReadTimeout: time.Duration(selConn.ExecTimeout) * time.Second,
|
|
||||||
WriteTimeout: time.Duration(selConn.ExecTimeout) * time.Second,
|
|
||||||
})
|
|
||||||
rdb.AddHook(redis2.NewHook(connName, func(cmd string, cost int64) {
|
rdb.AddHook(redis2.NewHook(connName, func(cmd string, cost int64) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
//last := strings.LastIndex(cmd, ":")
|
//last := strings.LastIndex(cmd, ":")
|
||||||
|
|
|
@ -3,17 +3,18 @@ package types
|
||||||
type ConnectionCategory int
|
type ConnectionCategory int
|
||||||
|
|
||||||
type ConnectionConfig struct {
|
type ConnectionConfig struct {
|
||||||
Name string `json:"name" yaml:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Group string `json:"group,omitempty" yaml:"-"`
|
Group string `json:"group,omitempty" yaml:"-"`
|
||||||
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"`
|
||||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
||||||
DefaultFilter string `json:"defaultFilter,omitempty" yaml:"default_filter,omitempty"`
|
DefaultFilter string `json:"defaultFilter,omitempty" yaml:"default_filter,omitempty"`
|
||||||
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"`
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
|
@ -35,3 +36,14 @@ type ConnectionDB struct {
|
||||||
Expires int `json:"expires,omitempty"`
|
Expires int `json:"expires,omitempty"`
|
||||||
AvgTTL int `json:"avgTtl,omitempty"`
|
AvgTTL int `json:"avgTtl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionSSH struct {
|
||||||
|
Enable bool `json:"enable" yaml:"enable"`
|
||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
|
@ -76,16 +76,17 @@ watch(
|
||||||
)
|
)
|
||||||
|
|
||||||
const borderRadius = computed(() => {
|
const borderRadius = computed(() => {
|
||||||
if (isMacOS()) {
|
// FIXME: cannot get full screen status sync?
|
||||||
return WindowIsFullscreen().then((full) => {
|
// if (isMacOS()) {
|
||||||
return full ? '0' : '10px'
|
// return WindowIsFullscreen().then((full) => {
|
||||||
})
|
// return full ? '0' : '10px'
|
||||||
}
|
// })
|
||||||
|
// }
|
||||||
return '10px'
|
return '10px'
|
||||||
})
|
})
|
||||||
|
|
||||||
const border = computed(() => {
|
const border = computed(() => {
|
||||||
const color = isMacOS() ? '#0000' : themeVars.value.borderColor
|
const color = isMacOS() && false ? '#0000' : themeVars.value.borderColor
|
||||||
return `1px solid ${color}`
|
return `1px solid ${color}`
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { every, get, includes, isEmpty, map } from 'lodash'
|
import { every, get, includes, isEmpty, map } 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 { TestConnection } from 'wailsjs/go/services/connectionService.js'
|
import { 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'
|
||||||
|
@ -56,6 +56,19 @@ const groupOptions = computed(() => {
|
||||||
return options
|
return options
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const sshLoginType = computed(() => {
|
||||||
|
return get(generalForm.value, 'ssh.loginType', 'pwd')
|
||||||
|
})
|
||||||
|
|
||||||
|
const onChoosePKFile = async () => {
|
||||||
|
const { success, data } = await SelectKeyFile(i18n.t('dialogue.connection.pkfile_selection_title'))
|
||||||
|
if (!success) {
|
||||||
|
generalForm.value.ssh.pkFile = ''
|
||||||
|
} else {
|
||||||
|
generalForm.value.ssh.pkFile = get(data, 'path', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tab = ref('general')
|
const tab = ref('general')
|
||||||
const testing = ref(false)
|
const testing = ref(false)
|
||||||
const showTestResult = ref(false)
|
const showTestResult = ref(false)
|
||||||
|
@ -79,6 +92,23 @@ const onSaveConnection = async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// trim ssh login data
|
||||||
|
if (generalForm.value.ssh.enable) {
|
||||||
|
switch (generalForm.value.ssh.loginType) {
|
||||||
|
case 'pkfile':
|
||||||
|
generalForm.value.ssh.password = ''
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
generalForm.value.ssh.pkFile = ''
|
||||||
|
generalForm.value.ssh.passphrase = ''
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ssh disabled, reset to default value
|
||||||
|
const { ssh } = connectionStore.newDefaultConnection()
|
||||||
|
generalForm.value.ssh = ssh
|
||||||
|
}
|
||||||
|
|
||||||
// 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,
|
||||||
|
@ -107,6 +137,7 @@ watch(
|
||||||
if (visible) {
|
if (visible) {
|
||||||
editName.value = get(dialogStore.connParam, 'name', '')
|
editName.value = get(dialogStore.connParam, 'name', '')
|
||||||
generalForm.value = dialogStore.connParam || connectionStore.newDefaultConnection()
|
generalForm.value = dialogStore.connParam || connectionStore.newDefaultConnection()
|
||||||
|
generalForm.value.ssh.loginType = generalForm.value.ssh.loginType || 'pwd'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -116,8 +147,7 @@ const onTestConnection = async () => {
|
||||||
testing.value = true
|
testing.value = true
|
||||||
let result = ''
|
let result = ''
|
||||||
try {
|
try {
|
||||||
const { addr, port, username, password } = generalForm.value
|
const { success = false, msg } = await TestConnection(generalForm.value)
|
||||||
const { success = false, msg } = await TestConnection(addr, port, username, password)
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
result = msg
|
result = msg
|
||||||
}
|
}
|
||||||
|
@ -187,7 +217,9 @@ const onClose = () => {
|
||||||
type="password" />
|
type="password" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item :label="$t('dialogue.connection.usr')" path="username">
|
<n-form-item :label="$t('dialogue.connection.usr')" path="username">
|
||||||
<n-input v-model="generalForm.username" :placeholder="$t('dialogue.connection.usr_tip')" />
|
<n-input
|
||||||
|
v-model:value="generalForm.username"
|
||||||
|
:placeholder="$t('dialogue.connection.usr_tip')" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
@ -238,13 +270,80 @@ const onClose = () => {
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</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-form-item label-placement="left">
|
||||||
|
<n-checkbox v-model:checked="generalForm.ssh.enable" size="medium">
|
||||||
|
{{ $t('dialogue.connection.ssh_enable') }}
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form
|
||||||
|
ref="sshFormRef"
|
||||||
|
:model="generalForm.ssh"
|
||||||
|
:show-require-mark="false"
|
||||||
|
:disabled="!generalForm.ssh.enable"
|
||||||
|
label-placement="top">
|
||||||
|
<n-form-item :label="$t('dialogue.connection.addr')" required>
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssh.addr"
|
||||||
|
:placeholder="$t('dialogue.connection.addr_tip')" />
|
||||||
|
<n-text style="width: 40px; text-align: center">:</n-text>
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="generalForm.ssh.port"
|
||||||
|
:max="65535"
|
||||||
|
:min="1"
|
||||||
|
style="width: 200px" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('dialogue.connection.login_type')">
|
||||||
|
<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.pkfile')" value="pkfile" />
|
||||||
|
</n-radio-group>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item
|
||||||
|
v-if="sshLoginType === 'pwd' || sshLoginType === 'pkfile'"
|
||||||
|
:label="$t('dialogue.connection.usr')">
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssh.username"
|
||||||
|
:placeholder="$t('dialogue.connection.ssh_usr_tip')" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="sshLoginType === 'pwd'" :label="$t('dialogue.connection.pwd')">
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssh.password"
|
||||||
|
:placeholder="$t('dialogue.connection.ssh_pwd_tip')"
|
||||||
|
show-password-on="click"
|
||||||
|
type="password" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.pkfile')">
|
||||||
|
<n-input-group>
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssh.pkFile"
|
||||||
|
:placeholder="$t('dialogue.connection.pkfile_tip')" />
|
||||||
|
<n-button :focusable="false" @click="onChoosePKFile">...</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.passphrase')">
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssh.passphrase"
|
||||||
|
:placeholder="$t('dialogue.connection.passphrase_tip')"
|
||||||
|
show-password-on="click"
|
||||||
|
type="password" />
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<!-- TODO: SSL tab pane -->
|
||||||
|
<!-- TODO: Sentinel tab pane -->
|
||||||
|
<!-- TODO: Cluster tab pane -->
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
|
|
||||||
<!-- test result alert-->
|
<!-- test result alert-->
|
||||||
<n-alert
|
<n-alert
|
||||||
v-if="showTestResult"
|
v-if="showTestResult"
|
||||||
:title="isEmpty(testResult) ? '' : $t('dialogue.connection.test_fail')"
|
:title="isEmpty(testResult) ? '' : $t('dialogue.connection.test_fail')"
|
||||||
:type="isEmpty(testResult) ? 'success' : 'error'">
|
:type="isEmpty(testResult) ? 'success' : 'error'"
|
||||||
|
closable
|
||||||
|
:on-close="() => (showTestResult = false)">
|
||||||
<template v-if="isEmpty(testResult)">{{ $t('dialogue.connection.test_succ') }}</template>
|
<template v-if="isEmpty(testResult)">{{ $t('dialogue.connection.test_succ') }}</template>
|
||||||
<template v-else>{{ testResult }}</template>
|
<template v-else>{{ testResult }}</template>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"sort_conn": "Sort Connections",
|
"sort_conn": "Sort Connections",
|
||||||
|
"new_conn_title": "New Connection",
|
||||||
"open_db": "Open Database",
|
"open_db": "Open Database",
|
||||||
"close_db": "Close Database",
|
"close_db": "Close Database",
|
||||||
"filter_key": "Filter Key",
|
"filter_key": "Filter Key",
|
||||||
|
@ -131,7 +132,17 @@
|
||||||
"advn_separator_tip": "Separator used for key path item",
|
"advn_separator_tip": "Separator used for key path item",
|
||||||
"advn_conn_timeout": "Connection Timeout",
|
"advn_conn_timeout": "Connection Timeout",
|
||||||
"advn_exec_timeout": "Execution Timeout",
|
"advn_exec_timeout": "Execution Timeout",
|
||||||
"advn_mark_color": "Mark Color"
|
"advn_mark_color": "Mark Color",
|
||||||
|
"ssh_enable": "Enable SSH Tuntel",
|
||||||
|
"ssh_tunnel": "SSH Tunnel",
|
||||||
|
"login_type": "Login Type",
|
||||||
|
"pkfile": "Private Key File",
|
||||||
|
"passphrase": "Passphrase",
|
||||||
|
"ssh_usr_tip": "SSH Username",
|
||||||
|
"ssh_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",
|
||||||
|
|
|
@ -132,7 +132,17 @@
|
||||||
"advn_separator_tip": "键名路径分隔符",
|
"advn_separator_tip": "键名路径分隔符",
|
||||||
"advn_conn_timeout": "连接超时",
|
"advn_conn_timeout": "连接超时",
|
||||||
"advn_exec_timeout": "执行超时",
|
"advn_exec_timeout": "执行超时",
|
||||||
"advn_mark_color": "标记颜色"
|
"advn_mark_color": "标记颜色",
|
||||||
|
"ssh_enable": "启用SSH隧道",
|
||||||
|
"ssh_tunnel": "SSH隧道",
|
||||||
|
"login_type": "登录类型",
|
||||||
|
"pkfile": "私钥文件",
|
||||||
|
"passphrase": "私钥密码",
|
||||||
|
"ssh_usr_tip": "SSH登录用户名",
|
||||||
|
"ssh_pwd_tip": "SSH登录密码",
|
||||||
|
"pkfile_tip": "SSH私钥文件路径",
|
||||||
|
"passphrase_tip": "(可选)SSH私钥密码",
|
||||||
|
"pkfile_selection_title": "请选择私钥文件"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"name": "分组名",
|
"name": "分组名",
|
||||||
|
|
|
@ -209,6 +209,16 @@ const useConnectionStore = defineStore('connections', {
|
||||||
connTimeout: 60,
|
connTimeout: 60,
|
||||||
execTimeout: 60,
|
execTimeout: 60,
|
||||||
markColor: '',
|
markColor: '',
|
||||||
|
ssh: {
|
||||||
|
enable: false,
|
||||||
|
addr: '',
|
||||||
|
port: 22,
|
||||||
|
loginType: 'pwd',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
pkFile: '',
|
||||||
|
passphrase: '',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -7,6 +7,7 @@ require (
|
||||||
github.com/redis/go-redis/v9 v9.2.0
|
github.com/redis/go-redis/v9 v9.2.0
|
||||||
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68
|
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68
|
||||||
github.com/wailsapp/wails/v2 v2.6.0
|
github.com/wailsapp/wails/v2 v2.6.0
|
||||||
|
golang.org/x/crypto v0.12.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +38,6 @@ require (
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/wailsapp/go-webview2 v1.0.5 // indirect
|
github.com/wailsapp/go-webview2 v1.0.5 // indirect
|
||||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||||
golang.org/x/crypto v0.12.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
|
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
|
||||||
golang.org/x/net v0.14.0 // indirect
|
golang.org/x/net v0.14.0 // indirect
|
||||||
golang.org/x/sys v0.11.0 // indirect
|
golang.org/x/sys v0.11.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -107,6 +107,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||||
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
|
Loading…
Reference in New Issue