Compare commits

...

5 Commits

Author SHA1 Message Date
tiny-craft 30e7016aa3 fix: database index confused after setting database filter 2023-10-08 18:25:52 +08:00
tiny-craft b5dfe377fa feat: add discovery master group name for sentinel mode config
perf: tidy connection profile file
2023-10-08 15:24:08 +08:00
tiny-craft ee68d699fa feat: support sentinel mode #16 #42 2023-10-08 01:33:03 +08:00
tiny-craft 477ed19d20 perf: update version compare logic 2023-10-07 23:37:12 +08:00
tiny-craft d2aa9317b9 chore: update dependencies to latest 2023-10-07 22:15:06 +08:00
16 changed files with 499 additions and 203 deletions

View File

@ -9,15 +9,16 @@ import (
"golang.org/x/crypto/ssh"
"net"
"os"
"slices"
"strconv"
"strings"
"sync"
"time"
. "tinyrdm/backend/storage"
"tinyrdm/backend/types"
"tinyrdm/backend/utils/coll"
maputil "tinyrdm/backend/utils/map"
redis2 "tinyrdm/backend/utils/redis"
sliceutil "tinyrdm/backend/utils/slice"
strutil "tinyrdm/backend/utils/string"
)
@ -74,7 +75,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 +128,70 @@ 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)
defer sentinel.Close()
var addr []string
addr, err = sentinel.GetMasterAddrByName(c.ctx, config.Sentinel.Master).Result()
if err != nil {
return nil, err
}
if len(addr) < 2 {
return nil, errors.New("cannot get master address")
}
option.Addr = fmt.Sprintf("%s:%s", addr[0], addr[1])
option.Username = config.Sentinel.Username
option.Password = config.Sentinel.Password
}
rdb := redis.NewClient(option)
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 {
@ -138,7 +199,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
@ -291,6 +353,7 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
dbInfo := c.parseDBItemInfo(dbInfoStr)
return types.ConnectionDB{
Name: dbName,
Index: idx,
Keys: dbInfo["keys"],
Expires: dbInfo["expires"],
AvgTTL: dbInfo["avg_ttl"],
@ -298,17 +361,20 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
} else {
return types.ConnectionDB{
Name: dbName,
Index: idx,
}
}
}
switch selConn.DBFilterType {
case "show":
for _, idx := range selConn.DBFilterList {
filterList := sliceutil.Unique(selConn.DBFilterList)
for _, idx := range filterList {
dbs = append(dbs, queryDB(idx))
}
case "hide":
hiddenList := coll.NewSet(selConn.DBFilterList...)
for idx := 0; idx < totaldb; idx++ {
if !slices.Contains(selConn.DBFilterList, idx) {
if !hiddenList.Contains(idx) {
dbs = append(dbs, queryDB(idx))
}
}

View File

@ -37,6 +37,9 @@ func (c *ConnectionsStorage) defaultConnectionItem() types.ConnectionConfig {
DBFilterType: "none",
DBFilterList: []int{},
MarkColor: "",
Sentinel: types.ConnectionSentinel{
Master: "mymaster",
},
}
}
@ -192,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
}
}
@ -284,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()
@ -330,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()

View File

@ -17,6 +17,7 @@ type ConnectionConfig struct {
DBFilterList []int `json:"dbFilterList" yaml:"db_filter_list,omitempty"`
MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"`
SSH ConnectionSSH `json:"ssh,omitempty" yaml:"ssh,omitempty"`
Sentinel ConnectionSentinel `json:"sentinel,omitempty" yaml:"sentinel,omitempty"`
}
type Connection struct {
@ -34,6 +35,7 @@ type ConnectionGroup struct {
type ConnectionDB struct {
Name string `json:"name"`
Index int `json:"index"`
Keys int `json:"keys"`
Expires int `json:"expires,omitempty"`
AvgTTL int `json:"avgTtl,omitempty"`
@ -44,8 +46,15 @@ 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"`
}
type ConnectionSentinel struct {
Enable bool `json:"enable" yaml:"enable"`
Master string `json:"master" yaml:"master"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
}

View File

@ -178,11 +178,11 @@ func decodeBinary(str string) (string, bool) {
}
func decodeHex(str string) (string, bool) {
encodeStr := hex.EncodeToString([]byte(str))
decodeStr := hex.EncodeToString([]byte(str))
var resultStr strings.Builder
for i := 0; i < len(encodeStr); i += 2 {
for i := 0; i < len(decodeStr); i += 2 {
resultStr.WriteString("\\x")
resultStr.WriteString(encodeStr[i : i+2])
resultStr.WriteString(decodeStr[i : i+2])
}
return resultStr.String(), true
}

View File

@ -12,18 +12,18 @@
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"pinia": "^2.1.6",
"sass": "^1.68.0",
"sass": "^1.69.0",
"vue": "^3.3.4",
"vue-i18n": "^9.4.1"
"vue-i18n": "^9.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
"naive-ui": "^2.34.4",
"@vitejs/plugin-vue": "^4.4.0",
"naive-ui": "^2.35.0",
"prettier": "^3.0.3",
"unplugin-auto-import": "^0.16.6",
"unplugin-icons": "^0.17.0",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.4.9"
"vite": "^4.4.11"
}
},
"node_modules/@antfu/install-pkg": {
@ -54,9 +54,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.22.15",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.15.tgz",
"integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"version": "7.23.1",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.1.tgz",
"integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -462,23 +462,23 @@
}
},
"node_modules/@intlify/core-base": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.4.1.tgz",
"integrity": "sha512-WIwx+elsZbxSMxRG5+LC+utRohFvmZMoDevfKOfnYMLbpCjCSavqTfHJAtfsY6ruowzqXeKkeLhRHbYbjoJx5g==",
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.5.0.tgz",
"integrity": "sha512-y3ufM1RJbI/DSmJf3lYs9ACq3S/iRvaSsE3rPIk0MGH7fp+JxU6rdryv/EYcwfcr3Y1aHFlCBir6S391hRZ57w==",
"dependencies": {
"@intlify/message-compiler": "9.4.1",
"@intlify/shared": "9.4.1"
"@intlify/message-compiler": "9.5.0",
"@intlify/shared": "9.5.0"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.4.1.tgz",
"integrity": "sha512-aN2N+dUx320108QhH51Ycd2LEpZ+NKbzyQ2kjjhqMcxhHdxtOnkgdx+MDBhOy/CObwBmhC3Nygzc6hNlfKvPNw==",
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.5.0.tgz",
"integrity": "sha512-CAhVNfEZcOVFg0/5MNyt+OFjvs4J/ARjCj2b+54/FvFP0EDJI5lIqMTSDBE7k0atMROSP0SvWCkwu/AZ5xkK1g==",
"dependencies": {
"@intlify/shared": "9.4.1",
"@intlify/shared": "9.5.0",
"source-map-js": "^1.0.2"
},
"engines": {
@ -486,9 +486,9 @@
}
},
"node_modules/@intlify/shared": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.4.1.tgz",
"integrity": "sha512-A51elBmZWf1FS80inf/32diO9DeXoqg9GR9aUDHFcfHoNDuT46Q+fpPOdj8jiJnSHSBh8E1E+6qWRhAZXdK3Ng==",
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.5.0.tgz",
"integrity": "sha512-tAxV14LMXZDZbu32XzLMTsowNlgJNmLwWHYzvMUl6L8gvQeoYiZONjY7AUsqZW8TOZDX9lfvF6adPkk9FSRdDA==",
"engines": {
"node": ">= 16"
}
@ -568,9 +568,9 @@
"dev": true
},
"node_modules/@types/katex": {
"version": "0.14.0",
"resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.14.0.tgz",
"integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==",
"version": "0.16.3",
"resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.3.tgz",
"integrity": "sha512-CeVMX9EhVUW8MWnei05eIRks4D5Wscw/W9Byz1s3PA+yJvcdvq9SaDjiUKvRvEgjpdTyJMjQA43ae4KTwsvOPg==",
"dev": true
},
"node_modules/@types/lodash": {
@ -589,9 +589,9 @@
}
},
"node_modules/@vitejs/plugin-vue": {
"version": "4.3.4",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz",
"integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==",
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz",
"integrity": "sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==",
"dev": true,
"engines": {
"node": "^14.18.0 || >=16.0.0"
@ -840,9 +840,9 @@
}
},
"node_modules/date-fns-tz": {
"version": "1.3.8",
"resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
"integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-2.0.0.tgz",
"integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==",
"dev": true,
"peerDependencies": {
"date-fns": ">=2.0.0"
@ -1254,22 +1254,22 @@
"dev": true
},
"node_modules/naive-ui": {
"version": "2.34.4",
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.34.4.tgz",
"integrity": "sha512-aPG8PDfhSzIzn/jSC9y3Jb3Pe2wHJ7F0cFV1EWlbImSrZECeUmoc+fIcOSWbizoztkKfaUAeKwYdMl09MKkj1g==",
"version": "2.35.0",
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.35.0.tgz",
"integrity": "sha512-PdnLpOip1LQaKs5+rXLZoPDPQkTq26TnHWeABvUA2eOQjtHxE4+TQvj0Jq/W8clM2On/7jptoGmenLt48G3Bhg==",
"dev": true,
"dependencies": {
"@css-render/plugin-bem": "^0.15.10",
"@css-render/vue3-ssr": "^0.15.10",
"@types/katex": "^0.14.0",
"@types/lodash": "^4.14.181",
"@types/lodash-es": "^4.17.6",
"async-validator": "^4.0.7",
"css-render": "^0.15.10",
"date-fns": "^2.28.0",
"date-fns-tz": "^1.3.3",
"@css-render/plugin-bem": "^0.15.12",
"@css-render/vue3-ssr": "^0.15.12",
"@types/katex": "^0.16.2",
"@types/lodash": "^4.14.198",
"@types/lodash-es": "^4.17.9",
"async-validator": "^4.2.5",
"css-render": "^0.15.12",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
"evtd": "^0.2.4",
"highlight.js": "^11.5.0",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"seemly": "^0.3.6",
@ -1545,9 +1545,9 @@
}
},
"node_modules/sass": {
"version": "1.68.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.68.0.tgz",
"integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==",
"version": "1.69.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.0.tgz",
"integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -1805,9 +1805,9 @@
}
},
"node_modules/vite": {
"version": "4.4.9",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.4.9.tgz",
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
"version": "4.4.11",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.4.11.tgz",
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
@ -1881,12 +1881,12 @@
}
},
"node_modules/vue-i18n": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.4.1.tgz",
"integrity": "sha512-vnQyYE9LBuNOqPpETIcCaGnAyLEqfeIvDcyZ9T+WBCWFTqWw1J8FuF1jfeDwpHBi5JKgAwgXyq1mt8jp/x/GPA==",
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.5.0.tgz",
"integrity": "sha512-NiI3Ph1qMstNf7uhYh8trQBOBFLxeJgcOxBq51pCcZ28Vs18Y7BDS58r8HGDKCYgXdLUYqPDXdKatIF4bvBVZg==",
"dependencies": {
"@intlify/core-base": "9.4.1",
"@intlify/shared": "9.4.1",
"@intlify/core-base": "9.5.0",
"@intlify/shared": "9.5.0",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
@ -1977,9 +1977,9 @@
"integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA=="
},
"@babel/runtime": {
"version": "7.22.15",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.15.tgz",
"integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==",
"version": "7.23.1",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.1.tgz",
"integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.14.0"
@ -2180,27 +2180,27 @@
}
},
"@intlify/core-base": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.4.1.tgz",
"integrity": "sha512-WIwx+elsZbxSMxRG5+LC+utRohFvmZMoDevfKOfnYMLbpCjCSavqTfHJAtfsY6ruowzqXeKkeLhRHbYbjoJx5g==",
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.5.0.tgz",
"integrity": "sha512-y3ufM1RJbI/DSmJf3lYs9ACq3S/iRvaSsE3rPIk0MGH7fp+JxU6rdryv/EYcwfcr3Y1aHFlCBir6S391hRZ57w==",
"requires": {
"@intlify/message-compiler": "9.4.1",
"@intlify/shared": "9.4.1"
"@intlify/message-compiler": "9.5.0",
"@intlify/shared": "9.5.0"
}
},
"@intlify/message-compiler": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.4.1.tgz",
"integrity": "sha512-aN2N+dUx320108QhH51Ycd2LEpZ+NKbzyQ2kjjhqMcxhHdxtOnkgdx+MDBhOy/CObwBmhC3Nygzc6hNlfKvPNw==",
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.5.0.tgz",
"integrity": "sha512-CAhVNfEZcOVFg0/5MNyt+OFjvs4J/ARjCj2b+54/FvFP0EDJI5lIqMTSDBE7k0atMROSP0SvWCkwu/AZ5xkK1g==",
"requires": {
"@intlify/shared": "9.4.1",
"@intlify/shared": "9.5.0",
"source-map-js": "^1.0.2"
}
},
"@intlify/shared": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.4.1.tgz",
"integrity": "sha512-A51elBmZWf1FS80inf/32diO9DeXoqg9GR9aUDHFcfHoNDuT46Q+fpPOdj8jiJnSHSBh8E1E+6qWRhAZXdK3Ng=="
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.5.0.tgz",
"integrity": "sha512-tAxV14LMXZDZbu32XzLMTsowNlgJNmLwWHYzvMUl6L8gvQeoYiZONjY7AUsqZW8TOZDX9lfvF6adPkk9FSRdDA=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
@ -2257,9 +2257,9 @@
"dev": true
},
"@types/katex": {
"version": "0.14.0",
"resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.14.0.tgz",
"integrity": "sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==",
"version": "0.16.3",
"resolved": "https://registry.npmmirror.com/@types/katex/-/katex-0.16.3.tgz",
"integrity": "sha512-CeVMX9EhVUW8MWnei05eIRks4D5Wscw/W9Byz1s3PA+yJvcdvq9SaDjiUKvRvEgjpdTyJMjQA43ae4KTwsvOPg==",
"dev": true
},
"@types/lodash": {
@ -2278,9 +2278,9 @@
}
},
"@vitejs/plugin-vue": {
"version": "4.3.4",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz",
"integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==",
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz",
"integrity": "sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==",
"dev": true,
"requires": {}
},
@ -2496,9 +2496,9 @@
}
},
"date-fns-tz": {
"version": "1.3.8",
"resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-1.3.8.tgz",
"integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==",
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/date-fns-tz/-/date-fns-tz-2.0.0.tgz",
"integrity": "sha512-OAtcLdB9vxSXTWHdT8b398ARImVwQMyjfYGkKD2zaGpHseG2UPHbHjXELReErZFxWdSLph3c2zOaaTyHfOhERQ==",
"dev": true,
"requires": {}
},
@ -2820,22 +2820,22 @@
"dev": true
},
"naive-ui": {
"version": "2.34.4",
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.34.4.tgz",
"integrity": "sha512-aPG8PDfhSzIzn/jSC9y3Jb3Pe2wHJ7F0cFV1EWlbImSrZECeUmoc+fIcOSWbizoztkKfaUAeKwYdMl09MKkj1g==",
"version": "2.35.0",
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.35.0.tgz",
"integrity": "sha512-PdnLpOip1LQaKs5+rXLZoPDPQkTq26TnHWeABvUA2eOQjtHxE4+TQvj0Jq/W8clM2On/7jptoGmenLt48G3Bhg==",
"dev": true,
"requires": {
"@css-render/plugin-bem": "^0.15.10",
"@css-render/vue3-ssr": "^0.15.10",
"@types/katex": "^0.14.0",
"@types/lodash": "^4.14.181",
"@types/lodash-es": "^4.17.6",
"async-validator": "^4.0.7",
"css-render": "^0.15.10",
"date-fns": "^2.28.0",
"date-fns-tz": "^1.3.3",
"@css-render/plugin-bem": "^0.15.12",
"@css-render/vue3-ssr": "^0.15.12",
"@types/katex": "^0.16.2",
"@types/lodash": "^4.14.198",
"@types/lodash-es": "^4.17.9",
"async-validator": "^4.2.5",
"css-render": "^0.15.12",
"date-fns": "^2.30.0",
"date-fns-tz": "^2.0.0",
"evtd": "^0.2.4",
"highlight.js": "^11.5.0",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"seemly": "^0.3.6",
@ -3025,9 +3025,9 @@
}
},
"sass": {
"version": "1.68.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.68.0.tgz",
"integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==",
"version": "1.69.0",
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.0.tgz",
"integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@ -3203,9 +3203,9 @@
}
},
"vite": {
"version": "4.4.9",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.4.9.tgz",
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
"version": "4.4.11",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.4.11.tgz",
"integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==",
"dev": true,
"requires": {
"esbuild": "^0.18.10",
@ -3236,12 +3236,12 @@
}
},
"vue-i18n": {
"version": "9.4.1",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.4.1.tgz",
"integrity": "sha512-vnQyYE9LBuNOqPpETIcCaGnAyLEqfeIvDcyZ9T+WBCWFTqWw1J8FuF1jfeDwpHBi5JKgAwgXyq1mt8jp/x/GPA==",
"version": "9.5.0",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.5.0.tgz",
"integrity": "sha512-NiI3Ph1qMstNf7uhYh8trQBOBFLxeJgcOxBq51pCcZ28Vs18Y7BDS58r8HGDKCYgXdLUYqPDXdKatIF4bvBVZg==",
"requires": {
"@intlify/core-base": "9.4.1",
"@intlify/shared": "9.4.1",
"@intlify/core-base": "9.5.0",
"@intlify/shared": "9.5.0",
"@vue/devtools-api": "^6.5.0"
}
},

View File

@ -13,17 +13,17 @@
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
"pinia": "^2.1.6",
"sass": "^1.68.0",
"sass": "^1.69.0",
"vue": "^3.3.4",
"vue-i18n": "^9.4.1"
"vue-i18n": "^9.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
"naive-ui": "^2.34.4",
"@vitejs/plugin-vue": "^4.4.0",
"naive-ui": "^2.35.0",
"prettier": "^3.0.3",
"unplugin-auto-import": "^0.16.6",
"unplugin-icons": "^0.17.0",
"unplugin-vue-components": "^0.25.2",
"vite": "^4.4.9"
"vite": "^4.4.11"
}
}

View File

@ -1 +1 @@
a65375421b9b10cadef51ed8edc1c6f1
82f42b67ae979cb1af64c05c79c5251f

View File

@ -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(
@ -198,6 +251,7 @@ const onClose = () => {
transform-origin="center">
<n-spin :show="closingConnection">
<n-tabs v-model:value="tab" animated type="line">
<!-- General pane -->
<n-tab-pane :tab="$t('dialogue.connection.general')" display-directive="show" name="general">
<n-form
ref="generalFormRef"
@ -239,7 +293,8 @@ const onClose = () => {
</n-form>
</n-tab-pane>
<n-tab-pane :tab="$t('dialogue.connection.advanced')" display-directive="show" name="advanced">
<!-- Advance pane -->
<n-tab-pane :tab="$t('dialogue.connection.advn.title')" display-directive="show" name="advanced">
<n-form
ref="advanceFormRef"
:model="generalForm"
@ -284,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')"
@ -294,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'"
@ -304,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"
@ -327,14 +386,14 @@ const onClose = () => {
</n-form>
</n-tab-pane>
<n-tab-pane :tab="$t('dialogue.connection.ssh.tunnel')" display-directive="show" name="ssh">
<!-- SSH pane -->
<n-tab-pane :tab="$t('dialogue.connection.ssh.title')" 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"
@ -388,8 +447,46 @@ const onClose = () => {
</n-form>
</n-tab-pane>
<!-- Sentinel pane -->
<n-tab-pane :tab="$t('dialogue.connection.sentinel.title')" display-directive="show" name="sentinel">
<n-form-item label-placement="left">
<n-checkbox v-model:checked="generalForm.sentinel.enable" size="medium">
{{ $t('dialogue.connection.sentinel.enable') }}
</n-checkbox>
</n-form-item>
<n-form
:model="generalForm.sentinel"
:show-require-mark="false"
:disabled="!generalForm.sentinel.enable"
label-placement="top">
<n-form-item :label="$t('dialogue.connection.sentinel.master')">
<n-input-group>
<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-input-group>
</n-form-item>
<n-form-item :label="$t('dialogue.connection.sentinel.password')">
<n-input
v-model:value="generalForm.sentinel.password"
:placeholder="$t('dialogue.connection.sentinel.pwd_tip')"
show-password-on="click"
type="password" />
</n-form-item>
<n-form-item :label="$t('dialogue.connection.sentinel.username')">
<n-input
v-model:value="generalForm.sentinel.username"
:placeholder="$t('dialogue.connection.sentinel.usr_tip')" />
</n-form-item>
</n-form>
</n-tab-pane>
<!-- TODO: SSL tab pane -->
<!-- TODO: Sentinel tab pane -->
<!-- TODO: Cluster tab pane -->
</n-tabs>

View File

@ -114,7 +114,6 @@
"new_title": "New Connection",
"edit_title": "Edit Connection",
"general": "General",
"advanced": "Advanced",
"no_group": "No Group",
"group": "Group",
"conn_name": "Name",
@ -123,12 +122,13 @@
"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",
"advn": {
"title": "Advanced",
"filter": "Default Key Filter Pattern",
"filter_tip": "Pattern which defines loaded keys from redis server",
"separator": "Key Separator",
@ -146,8 +146,8 @@
"mark_color": "Mark Color"
},
"ssh": {
"title": "SSH Tunnel",
"enable": "Enable SSH Tuntel",
"tunnel": "SSH Tunnel",
"login_type": "Login Type",
"pkfile": "Private Key File",
"passphrase": "Passphrase",
@ -157,6 +157,16 @@
"pkfile_tip": "SSH Private Key File Path",
"passphrase_tip": "(Optional) Passphrase for Private Key",
"pkfile_selection_title": "Please Select Private Key File"
},
"sentinel": {
"title": "Sentinel",
"enable": "Serve as Sentinel Node",
"master": "Master Group Name",
"auto_discover": "Auto Discover",
"password": "Password for Master Node",
"username": "Username for Master Node",
"pwd_tip": "(Optional) Authentication username for master node",
"usr_tip": "(Optional) Authentication password for master node (Redis > 6.0)"
}
},
"group": {

View File

@ -114,7 +114,6 @@
"new_title": "新建连接",
"edit_title": "编辑连接",
"general": "常规配置",
"advanced": "高级配置",
"no_group": "无分组",
"group": "分组",
"conn_name": "连接名",
@ -129,10 +128,11 @@
"test_succ": "成功连接到Redis服务器",
"test_fail": "连接失败",
"advn": {
"title": "高级配置",
"filter": "默认键过滤表达式",
"filter_tip": "需要加载的键名表达式",
"separator": "键分隔符",
"separator_tip":"键名路径分隔符",
"separator_tip": "键名路径分隔符",
"conn_timeout": "连接超时",
"exec_timeout": "执行超时",
"dbfilter_type": "数据库过滤方式",
@ -147,7 +147,7 @@
},
"ssh": {
"enable": "启用SSH隧道",
"tunnel": "SSH隧道",
"title": "SSH隧道",
"login_type": "登录类型",
"pkfile": "私钥文件",
"passphrase": "私钥密码",
@ -157,6 +157,16 @@
"pkfile_tip": "SSH私钥文件路径",
"passphrase_tip": "(可选)SSH私钥密码",
"pkfile_selection_title": "请选择私钥文件"
},
"sentinel": {
"title": "哨兵模式",
"enable": "当前为哨兵节点",
"master": "主节点组名",
"auto_discover": "自动查询组名",
"password": "主节点密码",
"username": "主节点用户名",
"pwd_tip": "(可选)主节点服务授权用户名",
"usr_tip": "(可选)主节点服务授权密码 (Redis > 6.0)"
}
},
"group": {
@ -200,7 +210,7 @@
"ttl": {
"title": "设置键存活时间"
},
"upgrade":{
"upgrade": {
"title": "有可用新版本",
"new_version_tip": "新版本({ver}),是否立即下载",
"no_update": "当前已是最新版",

View File

@ -1,5 +1,19 @@
import { defineStore } from 'pinia'
import { endsWith, get, isEmpty, join, remove, size, slice, sortedIndexBy, split, sumBy, toUpper, uniq } from 'lodash'
import {
endsWith,
find,
get,
isEmpty,
join,
remove,
size,
slice,
sortedIndexBy,
split,
sumBy,
toUpper,
uniq,
} from 'lodash'
import {
AddHashField,
AddListItem,
@ -221,9 +235,34 @@ const useConnectionStore = defineStore('connections', {
pkFile: '',
passphrase: '',
},
sentinel: {
enable: false,
master: 'mymaster',
username: '',
password: '',
},
}
},
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
@ -246,6 +285,23 @@ const useConnectionStore = defineStore('connections', {
return null
},
/**
* get database by server name and index
* @param {string} connName
* @param {number} db
* @return {{}|null}
*/
getDatabase(connName, db) {
const dbs = this.databases[connName]
if (dbs != null) {
const selDB = find(dbs, (item) => item.db === db)
if (selDB != null) {
return selDB
}
}
return null
},
/**
* create a new connection or update current connection profile
* @param {string} name set null if create a new connection
@ -337,7 +393,7 @@ const useConnectionStore = defineStore('connections', {
label: db[i].name,
name: name,
keys: db[i].keys,
db: i,
db: db[i].index,
type: ConnectionType.RedisDB,
isLeaf: false,
children: undefined,
@ -465,10 +521,14 @@ const useConnectionStore = defineStore('connections', {
throw new Error(msg)
}
const { keys = [] } = data
const dbs = this.databases[connName]
dbs[db].opened = true
const selDB = this.getDatabase(connName, db)
if (selDB == null) {
return
}
selDB.opened = true
if (isEmpty(keys)) {
dbs[db].children = []
selDB.children = []
return
}
@ -484,9 +544,12 @@ const useConnectionStore = defineStore('connections', {
* @returns {Promise<void>}
*/
async reopenDatabase(connName, db) {
const dbs = this.databases[connName]
dbs[db].children = undefined
dbs[db].isLeaf = false
const selDB = this.getDatabase(connName, db)
if (selDB == null) {
return
}
selDB.children = undefined
selDB.isLeaf = false
this._getNodeMap(connName, db).clear()
},
@ -497,10 +560,13 @@ const useConnectionStore = defineStore('connections', {
* @param db
*/
closeDatabase(connName, db) {
const dbs = this.databases[connName]
delete dbs[db].children
dbs[db].isLeaf = false
dbs[db].opened = false
const selDB = this.getDatabase(connName, db)
if (selDB == null) {
return
}
delete selDB.children
selDB.isLeaf = false
selDB.opened = false
this._getNodeMap(connName, db).clear()
},
@ -658,12 +724,16 @@ const useConnectionStore = defineStore('connections', {
return result
}
const separator = this._getSeparator(connName)
const dbs = this.databases[connName]
if (dbs[db].children == null) {
dbs[db].children = []
const selDB = this.getDatabase(connName, db)
if (selDB == null) {
return result
}
if (selDB.children == null) {
selDB.children = []
}
const nodeMap = this._getNodeMap(connName, db)
const rootChildren = dbs[db].children
const rootChildren = selDB.children
for (const key of keys) {
const keyParts = split(key, separator)
const len = size(keyParts)
@ -757,7 +827,7 @@ const useConnectionStore = defineStore('connections', {
*/
_tidyNode(connName, db, key, skipResort) {
const nodeMap = this._getNodeMap(connName, db)
const dbNode = get(this.databases, [connName, db], {})
const dbNode = this.getDatabase(connName, db) || {}
const separator = this._getSeparator(connName)
const keyParts = split(key, separator)
const totalParts = size(keyParts)
@ -848,7 +918,7 @@ const useConnectionStore = defineStore('connections', {
const nodeMap = this._getNodeMap(server, db)
return nodeMap.get(keyPart)
} else {
return this.databases[server][db]
return this.getDatabase(server, db)
}
},
@ -1266,7 +1336,7 @@ const useConnectionStore = defineStore('connections', {
* @private
*/
_deleteKeyNode(connName, db, key, isLayer) {
const dbRoot = get(this.databases, [connName, db], {})
const dbRoot = this.getDatabase(connName, db) || {}
const separator = this._getSeparator(connName)
if (dbRoot == null) {

View File

@ -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
},

View File

@ -12,6 +12,7 @@ import { BrowserOpenURL } from 'wailsjs/runtime/runtime.js'
import { i18nGlobal } from '@/utils/i18n.js'
import { enUS, NButton, NSpace, useOsTheme, zhCN } from 'naive-ui'
import { h, nextTick } from 'vue'
import { compareVersion } from '@/utils/version.js'
const osTheme = useOsTheme()
const usePreferencesStore = defineStore('preferences', {
@ -244,7 +245,11 @@ const usePreferencesStore = defineStore('preferences', {
const { success, data = {} } = await CheckForUpdate()
if (success) {
const { version = 'v1.0.0', latest, page_url: pageUrl } = data
if ((manual || latest > this.general.skipVersion) && latest > version && !isEmpty(pageUrl)) {
if (
(manual || latest > this.general.skipVersion) &&
compareVersion(latest, version) > 0 &&
!isEmpty(pageUrl)
) {
const notiRef = $notification.show({
title: i18nGlobal.t('dialogue.upgrade.title'),
content: i18nGlobal.t('dialogue.upgrade.new_version_tip', { ver: latest }),

View File

@ -0,0 +1,36 @@
import { get, isEmpty, map, size, split, trimStart } from 'lodash'
const toVerArr = (ver) => {
const v = trimStart(ver, 'v')
let vParts = split(v, '.')
if (isEmpty(vParts)) {
vParts = ['0']
}
return map(vParts, (v) => {
let vNum = parseInt(v)
return isNaN(vNum) ? 0 : vNum
})
}
/**
* compare two version strings
* @param {string} v1
* @param {string} v2
* @return {number}
*/
export const compareVersion = (v1, v2) => {
if (v1 !== v2) {
const v1Nums = toVerArr(v1)
const v2Nums = toVerArr(v2)
const length = Math.max(size(v1Nums), size(v2Nums))
for (let i = 0; i < length; i++) {
const num1 = get(v1Nums, i, 0)
const num2 = get(v2Nums, i, 0)
if (num1 !== num2) {
return num1 > num2 ? 1 : -1
}
}
}
return 0
}

8
go.mod
View File

@ -4,10 +4,10 @@ go 1.21
require (
github.com/adrg/sysfont v0.1.2
github.com/redis/go-redis/v9 v9.2.0
github.com/redis/go-redis/v9 v9.2.1
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68
github.com/wailsapp/wails/v2 v2.6.0
golang.org/x/crypto v0.12.0
golang.org/x/crypto v0.14.0
gopkg.in/yaml.v3 v3.0.1
)
@ -40,8 +40,8 @@ require (
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
)

20
go.sum
View File

@ -58,8 +58,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI=
github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@ -86,8 +86,8 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.6.0 h1:EyH0zR/EO6dDiqNy8qU5spaXDfkluiq77xrkabPYD4c=
github.com/wailsapp/wails/v2 v2.6.0/go.mod h1:WBG9KKWuw0FKfoepBrr/vRlyTmHaMibWesK3yz6nNiM=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -104,14 +104,14 @@ golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
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/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=