Compare commits
8 Commits
44f8581a41
...
4a0807e463
Author | SHA1 | Date |
---|---|---|
tiny-craft | 4a0807e463 | |
tiny-craft | 78bac6078a | |
tiny-craft | 7bcd78f321 | |
Lykin | c0bf201697 | |
tiny-craft | ad9f13d557 | |
tiny-craft | 2db858ba9e | |
tiny-craft | 444d0ea199 | |
ikaven1024 | 95af057dd5 |
|
@ -5,3 +5,4 @@ frontend/wailsjs
|
||||||
design/
|
design/
|
||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
|
test
|
||||||
|
|
27
app.go
27
app.go
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// App struct
|
|
||||||
type App struct {
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApp creates a new App application struct
|
|
||||||
func NewApp() *App {
|
|
||||||
return &App{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// startup is called when the app starts. The context is saved
|
|
||||||
// so we can call the runtime methods
|
|
||||||
func (a *App) startup(ctx context.Context) {
|
|
||||||
a.ctx = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// Greet returns a greeting for the given name
|
|
||||||
func (a *App) Greet(name string) string {
|
|
||||||
return fmt.Sprintf("Hello %s, It's show time!", name)
|
|
||||||
}
|
|
|
@ -2,16 +2,19 @@ package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"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"
|
"golang.org/x/crypto/ssh"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
. "tinyrdm/backend/storage"
|
. "tinyrdm/backend/storage"
|
||||||
"tinyrdm/backend/types"
|
"tinyrdm/backend/types"
|
||||||
|
@ -37,7 +40,7 @@ type connectionService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type connectionItem struct {
|
type connectionItem struct {
|
||||||
rdb *redis.Client
|
client redis.UniversalClient
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelFunc context.CancelFunc
|
cancelFunc context.CancelFunc
|
||||||
}
|
}
|
||||||
|
@ -67,9 +70,9 @@ func (c *connectionService) Start(ctx context.Context) {
|
||||||
|
|
||||||
func (c *connectionService) Stop(ctx context.Context) {
|
func (c *connectionService) Stop(ctx context.Context) {
|
||||||
for _, item := range c.connMap {
|
for _, item := range c.connMap {
|
||||||
if item.rdb != nil {
|
if item.client != nil {
|
||||||
item.cancelFunc()
|
item.cancelFunc()
|
||||||
item.rdb.Close()
|
item.client.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.connMap = map[string]connectionItem{}
|
c.connMap = map[string]connectionItem{}
|
||||||
|
@ -113,13 +116,48 @@ func (c *connectionService) buildOption(config types.ConnectionConfig) (*redis.O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if config.SSL.Enable {
|
||||||
|
// setup tls config
|
||||||
|
var certs []tls.Certificate
|
||||||
|
if len(config.SSL.CertFile) > 0 && len(config.SSL.KeyFile) > 0 {
|
||||||
|
if cert, err := tls.LoadX509KeyPair(config.SSL.CertFile, config.SSL.KeyFile); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
certs = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var caCertPool *x509.CertPool
|
||||||
|
if len(config.SSL.CAFile) > 0 {
|
||||||
|
ca, err := os.ReadFile(config.SSL.CAFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
caCertPool = x509.NewCertPool()
|
||||||
|
caCertPool.AppendCertsFromPEM(ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certs) <= 0 {
|
||||||
|
return nil, errors.New("tls config error")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig = &tls.Config{
|
||||||
|
RootCAs: caCertPool,
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
Certificates: certs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
option := &redis.Options{
|
option := &redis.Options{
|
||||||
|
ClientName: config.Name,
|
||||||
Addr: fmt.Sprintf("%s:%d", config.Addr, config.Port),
|
Addr: fmt.Sprintf("%s:%d", config.Addr, config.Port),
|
||||||
Username: config.Username,
|
Username: config.Username,
|
||||||
Password: config.Password,
|
Password: config.Password,
|
||||||
DialTimeout: time.Duration(config.ConnTimeout) * time.Second,
|
DialTimeout: time.Duration(config.ConnTimeout) * time.Second,
|
||||||
ReadTimeout: time.Duration(config.ExecTimeout) * time.Second,
|
ReadTimeout: time.Duration(config.ExecTimeout) * time.Second,
|
||||||
WriteTimeout: time.Duration(config.ExecTimeout) * time.Second,
|
WriteTimeout: time.Duration(config.ExecTimeout) * time.Second,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
if sshClient != nil {
|
if sshClient != nil {
|
||||||
option.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
option.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
@ -131,13 +169,14 @@ func (c *connectionService) buildOption(config types.ConnectionConfig) (*redis.O
|
||||||
return option, nil
|
return option, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*redis.Client, error) {
|
func (c *connectionService) createRedisClient(config types.ConnectionConfig) (redis.UniversalClient, error) {
|
||||||
option, err := c.buildOption(config)
|
option, err := c.buildOption(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Sentinel.Enable {
|
if config.Sentinel.Enable {
|
||||||
|
// get master address via sentinel node
|
||||||
sentinel := redis.NewSentinelClient(option)
|
sentinel := redis.NewSentinelClient(option)
|
||||||
defer sentinel.Close()
|
defer sentinel.Close()
|
||||||
|
|
||||||
|
@ -155,6 +194,57 @@ func (c *connectionService) createRedisClient(config types.ConnectionConfig) (*r
|
||||||
}
|
}
|
||||||
|
|
||||||
rdb := redis.NewClient(option)
|
rdb := redis.NewClient(option)
|
||||||
|
if config.Cluster.Enable {
|
||||||
|
// connect to cluster
|
||||||
|
var slots []redis.ClusterSlot
|
||||||
|
if slots, err = rdb.ClusterSlots(c.ctx).Result(); err == nil {
|
||||||
|
log.Println(slots)
|
||||||
|
clusterOptions := &redis.ClusterOptions{
|
||||||
|
//NewClient: nil,
|
||||||
|
//MaxRedirects: 0,
|
||||||
|
//RouteByLatency: false,
|
||||||
|
//RouteRandomly: false,
|
||||||
|
//ClusterSlots: nil,
|
||||||
|
ClientName: option.ClientName,
|
||||||
|
Dialer: option.Dialer,
|
||||||
|
OnConnect: option.OnConnect,
|
||||||
|
Protocol: option.Protocol,
|
||||||
|
Username: option.Username,
|
||||||
|
Password: option.Password,
|
||||||
|
MaxRetries: option.MaxRetries,
|
||||||
|
MinRetryBackoff: option.MinRetryBackoff,
|
||||||
|
MaxRetryBackoff: option.MaxRetryBackoff,
|
||||||
|
DialTimeout: option.DialTimeout,
|
||||||
|
ContextTimeoutEnabled: option.ContextTimeoutEnabled,
|
||||||
|
PoolFIFO: option.PoolFIFO,
|
||||||
|
PoolSize: option.PoolSize,
|
||||||
|
PoolTimeout: option.PoolTimeout,
|
||||||
|
MinIdleConns: option.MinIdleConns,
|
||||||
|
MaxIdleConns: option.MaxIdleConns,
|
||||||
|
ConnMaxIdleTime: option.ConnMaxIdleTime,
|
||||||
|
ConnMaxLifetime: option.ConnMaxLifetime,
|
||||||
|
TLSConfig: option.TLSConfig,
|
||||||
|
DisableIndentity: option.DisableIndentity,
|
||||||
|
}
|
||||||
|
if option.Dialer != nil {
|
||||||
|
clusterOptions.Dialer = option.Dialer
|
||||||
|
clusterOptions.ReadTimeout = -2
|
||||||
|
clusterOptions.WriteTimeout = -2
|
||||||
|
}
|
||||||
|
var addrs []string
|
||||||
|
for _, slot := range slots {
|
||||||
|
for _, node := range slot.Nodes {
|
||||||
|
addrs = append(addrs, node.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clusterOptions.Addrs = addrs
|
||||||
|
clusterClient := redis.NewClusterClient(clusterOptions)
|
||||||
|
return clusterClient, nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return rdb, nil
|
return rdb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,14 +283,14 @@ func (c *connectionService) ListSentinelMasters(config types.ConnectionConfig) (
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
client, err := c.createRedisClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rdb.Close()
|
defer client.Close()
|
||||||
|
|
||||||
if _, err = rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
|
if _, err = client.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
|
||||||
|
@ -266,23 +356,6 @@ 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)
|
||||||
|
@ -318,7 +391,7 @@ func (c *connectionService) DeleteGroup(name string, includeConn bool) (resp typ
|
||||||
|
|
||||||
// OpenConnection open redis server connection
|
// OpenConnection open redis server connection
|
||||||
func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
|
func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(name, 0)
|
client, ctx, err := c.getRedisClient(name, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -330,22 +403,51 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
|
||||||
var totaldb int
|
var totaldb int
|
||||||
if selConn.DBFilterType == "" || selConn.DBFilterType == "none" {
|
if selConn.DBFilterType == "" || selConn.DBFilterType == "none" {
|
||||||
// get total databases
|
// get total databases
|
||||||
if config, err := rdb.ConfigGet(ctx, "databases").Result(); err == nil {
|
if config, err := client.ConfigGet(ctx, "databases").Result(); err == nil {
|
||||||
if total, err := strconv.Atoi(config["databases"]); err == nil {
|
if total, err := strconv.Atoi(config["databases"]); err == nil {
|
||||||
totaldb = total
|
totaldb = total
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse all db, response content like below
|
||||||
|
var dbs []types.ConnectionDB
|
||||||
|
var clusterKeyCount int64
|
||||||
|
cluster, isCluster := client.(*redis.ClusterClient)
|
||||||
|
if isCluster {
|
||||||
|
var keyCount atomic.Int64
|
||||||
|
err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
|
if size, serr := cli.DBSize(ctx).Result(); serr != nil {
|
||||||
|
return serr
|
||||||
|
} else {
|
||||||
|
keyCount.Add(size)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = "get db size error:" + err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clusterKeyCount = keyCount.Load()
|
||||||
|
|
||||||
|
// only one database in cluster mode
|
||||||
|
dbs = []types.ConnectionDB{
|
||||||
|
{
|
||||||
|
Name: "db0",
|
||||||
|
Index: 0,
|
||||||
|
Keys: int(clusterKeyCount),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// get database info
|
// get database info
|
||||||
res, err := rdb.Info(ctx, "keyspace").Result()
|
var res string
|
||||||
|
res, err = client.Info(ctx, "keyspace").Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = "get server info fail:" + err.Error()
|
resp.Msg = "get server info fail:" + err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// parse all db, response content like below
|
|
||||||
var dbs []types.ConnectionDB
|
|
||||||
info := c.parseInfo(res)
|
info := c.parseInfo(res)
|
||||||
|
|
||||||
if totaldb <= 0 {
|
if totaldb <= 0 {
|
||||||
// cannot retrieve the database count by "CONFIG GET databases", try to get max index from keyspace
|
// cannot retrieve the database count by "CONFIG GET databases", try to get max index from keyspace
|
||||||
keyspace := info["Keyspace"]
|
keyspace := info["Keyspace"]
|
||||||
|
@ -379,6 +481,7 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch selConn.DBFilterType {
|
switch selConn.DBFilterType {
|
||||||
case "show":
|
case "show":
|
||||||
filterList := sliceutil.Unique(selConn.DBFilterList)
|
filterList := sliceutil.Unique(selConn.DBFilterList)
|
||||||
|
@ -397,6 +500,7 @@ func (c *connectionService) OpenConnection(name string) (resp types.JSResp) {
|
||||||
dbs = append(dbs, queryDB(idx))
|
dbs = append(dbs, queryDB(idx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
resp.Data = map[string]any{
|
resp.Data = map[string]any{
|
||||||
|
@ -410,9 +514,9 @@ func (c *connectionService) CloseConnection(name string) (resp types.JSResp) {
|
||||||
item, ok := c.connMap[name]
|
item, ok := c.connMap[name]
|
||||||
if ok {
|
if ok {
|
||||||
delete(c.connMap, name)
|
delete(c.connMap, name)
|
||||||
if item.rdb != nil {
|
if item.client != nil {
|
||||||
item.cancelFunc()
|
item.cancelFunc()
|
||||||
item.rdb.Close()
|
item.client.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
|
@ -421,24 +525,19 @@ func (c *connectionService) CloseConnection(name string) (resp types.JSResp) {
|
||||||
|
|
||||||
// get redis client from local cache or create a new open
|
// get redis client from local cache or create a new open
|
||||||
// if db >= 0, will also switch to db index
|
// if db >= 0, will also switch to db index
|
||||||
func (c *connectionService) getRedisClient(connName string, db int) (*redis.Client, context.Context, error) {
|
func (c *connectionService) getRedisClient(connName string, db int) (redis.UniversalClient, context.Context, error) {
|
||||||
item, ok := c.connMap[connName]
|
item, ok := c.connMap[connName]
|
||||||
var rdb *redis.Client
|
var client redis.UniversalClient
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
if ok {
|
if ok {
|
||||||
rdb, ctx = item.rdb, item.ctx
|
client, ctx = item.client, item.ctx
|
||||||
} else {
|
} else {
|
||||||
selConn := c.conns.GetConnection(connName)
|
selConn := c.conns.GetConnection(connName)
|
||||||
if selConn == nil {
|
if selConn == nil {
|
||||||
return nil, nil, fmt.Errorf("no match connection \"%s\"", connName)
|
return nil, nil, fmt.Errorf("no match connection \"%s\"", connName)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
hook := redis2.NewHook(connName, func(cmd string, cost int64) {
|
||||||
rdb, err = c.createRedisClient(selConn.ConnectionConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("create conenction error: %s", err.Error())
|
|
||||||
}
|
|
||||||
rdb.AddHook(redis2.NewHook(connName, func(cmd string, cost int64) {
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
//last := strings.LastIndex(cmd, ":")
|
//last := strings.LastIndex(cmd, ":")
|
||||||
//if last != -1 {
|
//if last != -1 {
|
||||||
|
@ -450,26 +549,48 @@ func (c *connectionService) getRedisClient(connName string, db int) (*redis.Clie
|
||||||
Cmd: cmd,
|
Cmd: cmd,
|
||||||
Cost: cost,
|
Cost: cost,
|
||||||
})
|
})
|
||||||
}))
|
})
|
||||||
|
|
||||||
if _, err = rdb.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
|
var err error
|
||||||
|
client, err = c.createRedisClient(selConn.ConnectionConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("create conenction error: %s", err.Error())
|
||||||
|
}
|
||||||
|
// add hook to each node in cluster mode
|
||||||
|
var cluster *redis.ClusterClient
|
||||||
|
if cluster, ok = client.(*redis.ClusterClient); ok {
|
||||||
|
err = cluster.ForEachShard(c.ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
|
cli.AddHook(hook)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("get cluster nodes error: %s", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.AddHook(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = client.Ping(c.ctx).Result(); err != nil && err != redis.Nil {
|
||||||
return nil, nil, errors.New("can not connect to redis server:" + err.Error())
|
return nil, nil, errors.New("can not connect to redis server:" + err.Error())
|
||||||
}
|
}
|
||||||
var cancelFunc context.CancelFunc
|
var cancelFunc context.CancelFunc
|
||||||
ctx, cancelFunc = context.WithCancel(c.ctx)
|
ctx, cancelFunc = context.WithCancel(c.ctx)
|
||||||
c.connMap[connName] = connectionItem{
|
c.connMap[connName] = connectionItem{
|
||||||
rdb: rdb,
|
client: client,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancelFunc: cancelFunc,
|
cancelFunc: cancelFunc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if db >= 0 {
|
if db >= 0 {
|
||||||
|
var rdb *redis.Client
|
||||||
|
if rdb, ok = client.(*redis.Client); ok && rdb != nil {
|
||||||
if err := rdb.Do(ctx, "select", strconv.Itoa(db)).Err(); err != nil {
|
if err := rdb.Do(ctx, "select", strconv.Itoa(db)).Err(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rdb, ctx, nil
|
}
|
||||||
|
return client, ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse command response content which use "redis info"
|
// parse command response content which use "redis info"
|
||||||
|
@ -511,14 +632,14 @@ func (c *connectionService) parseDBItemInfo(info string) map[string]int {
|
||||||
|
|
||||||
// ServerInfo get server info
|
// ServerInfo get server info
|
||||||
func (c *connectionService) ServerInfo(name string) (resp types.JSResp) {
|
func (c *connectionService) ServerInfo(name string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(name, 0)
|
client, ctx, err := c.getRedisClient(name, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get database info
|
// get database info
|
||||||
res, err := rdb.Info(ctx).Result()
|
res, err := client.Info(ctx).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = "get server info fail:" + err.Error()
|
resp.Msg = "get server info fail:" + err.Error()
|
||||||
return
|
return
|
||||||
|
@ -537,41 +658,48 @@ func (c *connectionService) OpenDatabase(connName string, db int, match string,
|
||||||
|
|
||||||
// ScanKeys scan all keys
|
// ScanKeys scan all keys
|
||||||
func (c *connectionService) ScanKeys(connName string, db int, match, keyType string) (resp types.JSResp) {
|
func (c *connectionService) ScanKeys(connName string, db int, match, keyType string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filterType := len(keyType) > 0
|
filterType := len(keyType) > 0
|
||||||
|
var countPerScan int64 = 10000
|
||||||
|
// define sub scan function
|
||||||
|
scan := func(ctx context.Context, cli redis.UniversalClient, appendFunc func(k any)) error {
|
||||||
|
var iter *redis.ScanIterator
|
||||||
|
if filterType {
|
||||||
|
iter = cli.ScanType(ctx, 0, match, countPerScan, keyType).Iterator()
|
||||||
|
} else {
|
||||||
|
iter = cli.Scan(ctx, 0, match, countPerScan).Iterator()
|
||||||
|
}
|
||||||
|
for iter.Next(ctx) {
|
||||||
|
appendFunc(strutil.EncodeRedisKey(iter.Val()))
|
||||||
|
}
|
||||||
|
return iter.Err()
|
||||||
|
}
|
||||||
|
|
||||||
var keys []any
|
var keys []any
|
||||||
//keys := map[string]keyItem{}
|
if cluster, ok := client.(*redis.ClusterClient); ok {
|
||||||
var cursor uint64
|
// cluster mode
|
||||||
for {
|
var mutex sync.Mutex
|
||||||
var loadedKey []string
|
err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
if filterType {
|
return scan(ctx, cli, func(k any) {
|
||||||
loadedKey, cursor, err = rdb.ScanType(ctx, cursor, match, 10000, keyType).Result()
|
mutex.Lock()
|
||||||
|
keys = append(keys, k)
|
||||||
|
mutex.Unlock()
|
||||||
|
})
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
loadedKey, cursor, err = rdb.Scan(ctx, cursor, match, 10000).Result()
|
err = scan(ctx, client, func(k any) {
|
||||||
|
keys = append(keys, k)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, k := range loadedKey {
|
|
||||||
keys = append(keys, strutil.EncodeRedisKey(k))
|
|
||||||
}
|
|
||||||
//for _, k := range loadedKey {
|
|
||||||
// //t, _ := rdb.Type(ctx, k).Result()
|
|
||||||
// keys[k] = keyItem{Type: "t"}
|
|
||||||
//}
|
|
||||||
//keys = append(keys, loadedKey...)
|
|
||||||
// no more loadedKey
|
|
||||||
if cursor == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
resp.Data = map[string]any{
|
resp.Data = map[string]any{
|
||||||
|
@ -582,7 +710,7 @@ func (c *connectionService) ScanKeys(connName string, db int, match, keyType str
|
||||||
|
|
||||||
// GetKeyValue get value by key
|
// GetKeyValue get value by key
|
||||||
func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs string) (resp types.JSResp) {
|
func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -591,7 +719,7 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
var keyType string
|
var keyType string
|
||||||
var dur time.Duration
|
var dur time.Duration
|
||||||
keyType, err = rdb.Type(ctx, key).Result()
|
keyType, err = client.Type(ctx, key).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -603,7 +731,7 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
}
|
}
|
||||||
|
|
||||||
var ttl int64
|
var ttl int64
|
||||||
if dur, err = rdb.TTL(ctx, key).Result(); err != nil {
|
if dur, err = client.TTL(ctx, key).Result(); err != nil {
|
||||||
ttl = -1
|
ttl = -1
|
||||||
} else {
|
} else {
|
||||||
if dur < 0 {
|
if dur < 0 {
|
||||||
|
@ -619,18 +747,18 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
switch strings.ToLower(keyType) {
|
switch strings.ToLower(keyType) {
|
||||||
case "string":
|
case "string":
|
||||||
var str string
|
var str string
|
||||||
str, err = rdb.Get(ctx, key).Result()
|
str, err = client.Get(ctx, key).Result()
|
||||||
value, viewAs = strutil.ConvertTo(str, viewAs)
|
value, viewAs = strutil.ConvertTo(str, viewAs)
|
||||||
size, _ = rdb.StrLen(ctx, key).Result()
|
size, _ = client.StrLen(ctx, key).Result()
|
||||||
case "list":
|
case "list":
|
||||||
value, err = rdb.LRange(ctx, key, 0, -1).Result()
|
value, err = client.LRange(ctx, key, 0, -1).Result()
|
||||||
size, _ = rdb.LLen(ctx, key).Result()
|
size, _ = client.LLen(ctx, key).Result()
|
||||||
case "hash":
|
case "hash":
|
||||||
//value, err = rdb.HGetAll(ctx, key).Result()
|
//value, err = client.HGetAll(ctx, key).Result()
|
||||||
items := map[string]string{}
|
items := map[string]string{}
|
||||||
for {
|
for {
|
||||||
var loadedVal []string
|
var loadedVal []string
|
||||||
loadedVal, cursor, err = rdb.HScan(ctx, key, cursor, "*", 10000).Result()
|
loadedVal, cursor, err = client.HScan(ctx, key, cursor, "*", 10000).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -643,13 +771,13 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = items
|
value = items
|
||||||
size, _ = rdb.HLen(ctx, key).Result()
|
size, _ = client.HLen(ctx, key).Result()
|
||||||
case "set":
|
case "set":
|
||||||
//value, err = rdb.SMembers(ctx, key).Result()
|
//value, err = client.SMembers(ctx, key).Result()
|
||||||
items := []string{}
|
items := []string{}
|
||||||
for {
|
for {
|
||||||
var loadedKey []string
|
var loadedKey []string
|
||||||
loadedKey, cursor, err = rdb.SScan(ctx, key, cursor, "*", 10000).Result()
|
loadedKey, cursor, err = client.SScan(ctx, key, cursor, "*", 10000).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -660,13 +788,13 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = items
|
value = items
|
||||||
size, _ = rdb.SCard(ctx, key).Result()
|
size, _ = client.SCard(ctx, key).Result()
|
||||||
case "zset":
|
case "zset":
|
||||||
//value, err = rdb.ZRangeWithScores(ctx, key, 0, -1).Result()
|
//value, err = client.ZRangeWithScores(ctx, key, 0, -1).Result()
|
||||||
var items []types.ZSetItem
|
var items []types.ZSetItem
|
||||||
for {
|
for {
|
||||||
var loadedVal []string
|
var loadedVal []string
|
||||||
loadedVal, cursor, err = rdb.ZScan(ctx, key, cursor, "*", 10000).Result()
|
loadedVal, cursor, err = client.ZScan(ctx, key, cursor, "*", 10000).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -685,11 +813,11 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = items
|
value = items
|
||||||
size, _ = rdb.ZCard(ctx, key).Result()
|
size, _ = client.ZCard(ctx, key).Result()
|
||||||
case "stream":
|
case "stream":
|
||||||
var msgs []redis.XMessage
|
var msgs []redis.XMessage
|
||||||
items := []types.StreamItem{}
|
items := []types.StreamItem{}
|
||||||
msgs, err = rdb.XRevRange(ctx, key, "+", "-").Result()
|
msgs, err = client.XRevRange(ctx, key, "+", "-").Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -701,7 +829,7 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
value = items
|
value = items
|
||||||
size, _ = rdb.XLen(ctx, key).Result()
|
size, _ = client.XLen(ctx, key).Result()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -721,7 +849,7 @@ func (c *connectionService) GetKeyValue(connName string, db int, k any, viewAs s
|
||||||
// SetKeyValue set value by key
|
// SetKeyValue set value by key
|
||||||
// @param ttl <= 0 means keep current ttl
|
// @param ttl <= 0 means keep current ttl
|
||||||
func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType string, value any, ttl int64, viewAs string) (resp types.JSResp) {
|
func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType string, value any, ttl int64, viewAs string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -730,7 +858,7 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
var expiration time.Duration
|
var expiration time.Duration
|
||||||
if ttl < 0 {
|
if ttl < 0 {
|
||||||
if expiration, err = rdb.PTTL(ctx, key).Result(); err != nil {
|
if expiration, err = client.PTTL(ctx, key).Result(); err != nil {
|
||||||
expiration = redis.KeepTTL
|
expiration = redis.KeepTTL
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -747,10 +875,10 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
resp.Msg = fmt.Sprintf(`save to "%s" type fail: %s`, viewAs, err.Error())
|
resp.Msg = fmt.Sprintf(`save to "%s" type fail: %s`, viewAs, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = rdb.Set(ctx, key, saveStr, 0).Result()
|
_, err = client.Set(ctx, key, saveStr, 0).Result()
|
||||||
// set expiration lonely, not "keepttl"
|
// set expiration lonely, not "keepttl"
|
||||||
if err == nil && expiration > 0 {
|
if err == nil && expiration > 0 {
|
||||||
rdb.Expire(ctx, key, expiration)
|
client.Expire(ctx, key, expiration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "list":
|
case "list":
|
||||||
|
@ -758,9 +886,9 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
resp.Msg = "invalid list value"
|
resp.Msg = "invalid list value"
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
err = rdb.LPush(ctx, key, strs...).Err()
|
err = client.LPush(ctx, key, strs...).Err()
|
||||||
if err == nil && expiration > 0 {
|
if err == nil && expiration > 0 {
|
||||||
rdb.Expire(ctx, key, expiration)
|
client.Expire(ctx, key, expiration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "hash":
|
case "hash":
|
||||||
|
@ -770,7 +898,7 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
} else {
|
} else {
|
||||||
total := len(strs)
|
total := len(strs)
|
||||||
if total > 1 {
|
if total > 1 {
|
||||||
_, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
_, err = client.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||||
for i := 0; i < total; i += 2 {
|
for i := 0; i < total; i += 2 {
|
||||||
pipe.HSet(ctx, key, strs[i], strs[i+1])
|
pipe.HSet(ctx, key, strs[i], strs[i+1])
|
||||||
}
|
}
|
||||||
|
@ -787,9 +915,9 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 {
|
||||||
err = rdb.SAdd(ctx, key, strs...).Err()
|
err = client.SAdd(ctx, key, strs...).Err()
|
||||||
if err == nil && expiration > 0 {
|
if err == nil && expiration > 0 {
|
||||||
rdb.Expire(ctx, key, expiration)
|
client.Expire(ctx, key, expiration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -807,9 +935,9 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
Member: strs[i],
|
Member: strs[i],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
err = rdb.ZAdd(ctx, key, members...).Err()
|
err = client.ZAdd(ctx, key, members...).Err()
|
||||||
if err == nil && expiration > 0 {
|
if err == nil && expiration > 0 {
|
||||||
rdb.Expire(ctx, key, expiration)
|
client.Expire(ctx, key, expiration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -819,13 +947,13 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if len(strs) > 2 {
|
if len(strs) > 2 {
|
||||||
err = rdb.XAdd(ctx, &redis.XAddArgs{
|
err = client.XAdd(ctx, &redis.XAddArgs{
|
||||||
Stream: key,
|
Stream: key,
|
||||||
ID: strs[0].(string),
|
ID: strs[0].(string),
|
||||||
Values: strs[1:],
|
Values: strs[1:],
|
||||||
}).Err()
|
}).Err()
|
||||||
if err == nil && expiration > 0 {
|
if err == nil && expiration > 0 {
|
||||||
rdb.Expire(ctx, key, expiration)
|
client.Expire(ctx, key, expiration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -844,7 +972,7 @@ func (c *connectionService) SetKeyValue(connName string, db int, k any, keyType
|
||||||
|
|
||||||
// SetHashValue set hash field
|
// SetHashValue set hash field
|
||||||
func (c *connectionService) SetHashValue(connName string, db int, k any, field, newField, value string) (resp types.JSResp) {
|
func (c *connectionService) SetHashValue(connName string, db int, k any, field, newField, value string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -855,23 +983,23 @@ func (c *connectionService) SetHashValue(connName string, db int, k any, field,
|
||||||
updatedField := map[string]string{}
|
updatedField := map[string]string{}
|
||||||
if len(field) <= 0 {
|
if len(field) <= 0 {
|
||||||
// old filed is empty, add new field
|
// old filed is empty, add new field
|
||||||
_, err = rdb.HSet(ctx, key, newField, value).Result()
|
_, err = client.HSet(ctx, key, newField, value).Result()
|
||||||
updatedField[newField] = value
|
updatedField[newField] = value
|
||||||
} else if len(newField) <= 0 {
|
} else if len(newField) <= 0 {
|
||||||
// new field is empty, delete old field
|
// new field is empty, delete old field
|
||||||
_, err = rdb.HDel(ctx, key, field, value).Result()
|
_, err = client.HDel(ctx, key, field, value).Result()
|
||||||
removedField = append(removedField, field)
|
removedField = append(removedField, field)
|
||||||
} else if field == newField {
|
} else if field == newField {
|
||||||
// replace field
|
// replace field
|
||||||
_, err = rdb.HSet(ctx, key, newField, value).Result()
|
_, err = client.HSet(ctx, key, newField, value).Result()
|
||||||
updatedField[newField] = value
|
updatedField[newField] = value
|
||||||
} else {
|
} else {
|
||||||
// remove old field and add new field
|
// remove old field and add new field
|
||||||
if _, err = rdb.HDel(ctx, key, field).Result(); err != nil {
|
if _, err = client.HDel(ctx, key, field).Result(); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = rdb.HSet(ctx, key, newField, value).Result()
|
_, err = client.HSet(ctx, key, newField, value).Result()
|
||||||
removedField = append(removedField, field)
|
removedField = append(removedField, field)
|
||||||
updatedField[newField] = value
|
updatedField[newField] = value
|
||||||
}
|
}
|
||||||
|
@ -890,7 +1018,7 @@ func (c *connectionService) SetHashValue(connName string, db int, k any, field,
|
||||||
|
|
||||||
// AddHashField add or update hash field
|
// AddHashField add or update hash field
|
||||||
func (c *connectionService) AddHashField(connName string, db int, k any, action int, fieldItems []any) (resp types.JSResp) {
|
func (c *connectionService) AddHashField(connName string, db int, k any, action int, fieldItems []any) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -902,7 +1030,7 @@ func (c *connectionService) AddHashField(connName string, db int, k any, action
|
||||||
case 1:
|
case 1:
|
||||||
// ignore duplicated fields
|
// ignore duplicated fields
|
||||||
for i := 0; i < len(fieldItems); i += 2 {
|
for i := 0; i < len(fieldItems); i += 2 {
|
||||||
_, err = rdb.HSetNX(ctx, key, fieldItems[i].(string), fieldItems[i+1]).Result()
|
_, err = client.HSetNX(ctx, key, fieldItems[i].(string), fieldItems[i+1]).Result()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
updated[fieldItems[i].(string)] = fieldItems[i+1]
|
updated[fieldItems[i].(string)] = fieldItems[i+1]
|
||||||
}
|
}
|
||||||
|
@ -911,9 +1039,9 @@ func (c *connectionService) AddHashField(connName string, db int, k any, action
|
||||||
// overwrite duplicated fields
|
// overwrite duplicated fields
|
||||||
total := len(fieldItems)
|
total := len(fieldItems)
|
||||||
if total > 1 {
|
if total > 1 {
|
||||||
_, err = rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
_, err = client.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||||
for i := 0; i < total; i += 2 {
|
for i := 0; i < total; i += 2 {
|
||||||
rdb.HSet(ctx, key, fieldItems[i], fieldItems[i+1])
|
client.HSet(ctx, key, fieldItems[i], fieldItems[i+1])
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -936,7 +1064,7 @@ func (c *connectionService) AddHashField(connName string, db int, k any, action
|
||||||
|
|
||||||
// AddListItem add item to list or remove from it
|
// AddListItem add item to list or remove from it
|
||||||
func (c *connectionService) AddListItem(connName string, db int, k any, action int, items []any) (resp types.JSResp) {
|
func (c *connectionService) AddListItem(connName string, db int, k any, action int, items []any) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -947,11 +1075,11 @@ func (c *connectionService) AddListItem(connName string, db int, k any, action i
|
||||||
switch action {
|
switch action {
|
||||||
case 0:
|
case 0:
|
||||||
// push to head
|
// push to head
|
||||||
_, err = rdb.LPush(ctx, key, items...).Result()
|
_, err = client.LPush(ctx, key, items...).Result()
|
||||||
leftPush = append(leftPush, items...)
|
leftPush = append(leftPush, items...)
|
||||||
default:
|
default:
|
||||||
// append to tail
|
// append to tail
|
||||||
_, err = rdb.RPush(ctx, key, items...).Result()
|
_, err = client.RPush(ctx, key, items...).Result()
|
||||||
rightPush = append(rightPush, items...)
|
rightPush = append(rightPush, items...)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -969,7 +1097,7 @@ func (c *connectionService) AddListItem(connName string, db int, k any, action i
|
||||||
|
|
||||||
// SetListItem update or remove list item by index
|
// SetListItem update or remove list item by index
|
||||||
func (c *connectionService) SetListItem(connName string, db int, k any, index int64, value string) (resp types.JSResp) {
|
func (c *connectionService) SetListItem(connName string, db int, k any, index int64, value string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -980,13 +1108,13 @@ func (c *connectionService) SetListItem(connName string, db int, k any, index in
|
||||||
updated := map[int64]string{}
|
updated := map[int64]string{}
|
||||||
if len(value) <= 0 {
|
if len(value) <= 0 {
|
||||||
// remove from list
|
// remove from list
|
||||||
err = rdb.LSet(ctx, key, index, "---VALUE_REMOVED_BY_TINY_RDM---").Err()
|
err = client.LSet(ctx, key, index, "---VALUE_REMOVED_BY_TINY_RDM---").Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rdb.LRem(ctx, key, 1, "---VALUE_REMOVED_BY_TINY_RDM---").Err()
|
err = client.LRem(ctx, key, 1, "---VALUE_REMOVED_BY_TINY_RDM---").Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -994,7 +1122,7 @@ func (c *connectionService) SetListItem(connName string, db int, k any, index in
|
||||||
removed = append(removed, index)
|
removed = append(removed, index)
|
||||||
} else {
|
} else {
|
||||||
// replace index value
|
// replace index value
|
||||||
err = rdb.LSet(ctx, key, index, value).Err()
|
err = client.LSet(ctx, key, index, value).Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1012,7 +1140,7 @@ func (c *connectionService) SetListItem(connName string, db int, k any, index in
|
||||||
|
|
||||||
// SetSetItem add members to set or remove from set
|
// SetSetItem add members to set or remove from set
|
||||||
func (c *connectionService) SetSetItem(connName string, db int, k any, remove bool, members []any) (resp types.JSResp) {
|
func (c *connectionService) SetSetItem(connName string, db int, k any, remove bool, members []any) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1020,9 +1148,9 @@ func (c *connectionService) SetSetItem(connName string, db int, k any, remove bo
|
||||||
|
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
if remove {
|
if remove {
|
||||||
_, err = rdb.SRem(ctx, key, members...).Result()
|
_, err = client.SRem(ctx, key, members...).Result()
|
||||||
} else {
|
} else {
|
||||||
_, err = rdb.SAdd(ctx, key, members...).Result()
|
_, err = client.SAdd(ctx, key, members...).Result()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -1035,15 +1163,15 @@ func (c *connectionService) SetSetItem(connName string, db int, k any, remove bo
|
||||||
|
|
||||||
// UpdateSetItem replace member of set
|
// UpdateSetItem replace member of set
|
||||||
func (c *connectionService) UpdateSetItem(connName string, db int, k any, value, newValue string) (resp types.JSResp) {
|
func (c *connectionService) UpdateSetItem(connName string, db int, k any, value, newValue string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
_, _ = rdb.SRem(ctx, key, value).Result()
|
_, _ = client.SRem(ctx, key, value).Result()
|
||||||
_, err = rdb.SAdd(ctx, key, newValue).Result()
|
_, err = client.SAdd(ctx, key, newValue).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1055,7 +1183,7 @@ func (c *connectionService) UpdateSetItem(connName string, db int, k any, value,
|
||||||
|
|
||||||
// UpdateZSetValue update value of sorted set member
|
// UpdateZSetValue update value of sorted set member
|
||||||
func (c *connectionService) UpdateZSetValue(connName string, db int, k any, value, newValue string, score float64) (resp types.JSResp) {
|
func (c *connectionService) UpdateZSetValue(connName string, db int, k any, value, newValue string, score float64) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1066,24 +1194,24 @@ func (c *connectionService) UpdateZSetValue(connName string, db int, k any, valu
|
||||||
var removed []string
|
var removed []string
|
||||||
if len(newValue) <= 0 {
|
if len(newValue) <= 0 {
|
||||||
// blank new value, delete value
|
// blank new value, delete value
|
||||||
_, err = rdb.ZRem(ctx, key, value).Result()
|
_, err = client.ZRem(ctx, key, value).Result()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
removed = append(removed, value)
|
removed = append(removed, value)
|
||||||
}
|
}
|
||||||
} else if newValue == value {
|
} else if newValue == value {
|
||||||
// update score only
|
// update score only
|
||||||
_, err = rdb.ZAdd(ctx, key, redis.Z{
|
_, err = client.ZAdd(ctx, key, redis.Z{
|
||||||
Score: score,
|
Score: score,
|
||||||
Member: value,
|
Member: value,
|
||||||
}).Result()
|
}).Result()
|
||||||
} else {
|
} else {
|
||||||
// remove old value and add new one
|
// remove old value and add new one
|
||||||
_, err = rdb.ZRem(ctx, key, value).Result()
|
_, err = client.ZRem(ctx, key, value).Result()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
removed = append(removed, value)
|
removed = append(removed, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rdb.ZAdd(ctx, key, redis.Z{
|
_, err = client.ZAdd(ctx, key, redis.Z{
|
||||||
Score: score,
|
Score: score,
|
||||||
Member: newValue,
|
Member: newValue,
|
||||||
}).Result()
|
}).Result()
|
||||||
|
@ -1106,7 +1234,7 @@ func (c *connectionService) UpdateZSetValue(connName string, db int, k any, valu
|
||||||
|
|
||||||
// AddZSetValue add item to sorted set
|
// AddZSetValue add item to sorted set
|
||||||
func (c *connectionService) AddZSetValue(connName string, db int, k any, action int, valueScore map[string]float64) (resp types.JSResp) {
|
func (c *connectionService) AddZSetValue(connName string, db int, k any, action int, valueScore map[string]float64) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1123,10 +1251,10 @@ func (c *connectionService) AddZSetValue(connName string, db int, k any, action
|
||||||
switch action {
|
switch action {
|
||||||
case 1:
|
case 1:
|
||||||
// ignore duplicated fields
|
// ignore duplicated fields
|
||||||
_, err = rdb.ZAddNX(ctx, key, members...).Result()
|
_, err = client.ZAddNX(ctx, key, members...).Result()
|
||||||
default:
|
default:
|
||||||
// overwrite duplicated fields
|
// overwrite duplicated fields
|
||||||
_, err = rdb.ZAdd(ctx, key, members...).Result()
|
_, err = client.ZAdd(ctx, key, members...).Result()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
|
@ -1139,14 +1267,14 @@ func (c *connectionService) AddZSetValue(connName string, db int, k any, action
|
||||||
|
|
||||||
// AddStreamValue add stream field
|
// AddStreamValue add stream field
|
||||||
func (c *connectionService) AddStreamValue(connName string, db int, k any, ID string, fieldItems []any) (resp types.JSResp) {
|
func (c *connectionService) AddStreamValue(connName string, db int, k any, ID string, fieldItems []any) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
_, err = rdb.XAdd(ctx, &redis.XAddArgs{
|
_, err = client.XAdd(ctx, &redis.XAddArgs{
|
||||||
Stream: key,
|
Stream: key,
|
||||||
ID: ID,
|
ID: ID,
|
||||||
Values: fieldItems,
|
Values: fieldItems,
|
||||||
|
@ -1162,21 +1290,21 @@ func (c *connectionService) AddStreamValue(connName string, db int, k any, ID st
|
||||||
|
|
||||||
// RemoveStreamValues remove stream values by id
|
// RemoveStreamValues remove stream values by id
|
||||||
func (c *connectionService) RemoveStreamValues(connName string, db int, k any, IDs []string) (resp types.JSResp) {
|
func (c *connectionService) RemoveStreamValues(connName string, db int, k any, IDs []string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
_, err = rdb.XDel(ctx, key, IDs...).Result()
|
_, err = client.XDel(ctx, key, IDs...).Result()
|
||||||
resp.Success = true
|
resp.Success = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetKeyTTL set ttl of key
|
// SetKeyTTL set ttl of key
|
||||||
func (c *connectionService) SetKeyTTL(connName string, db int, k any, ttl int64) (resp types.JSResp) {
|
func (c *connectionService) SetKeyTTL(connName string, db int, k any, ttl int64) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1185,13 +1313,13 @@ func (c *connectionService) SetKeyTTL(connName string, db int, k any, ttl int64)
|
||||||
key := strutil.DecodeRedisKey(k)
|
key := strutil.DecodeRedisKey(k)
|
||||||
var expiration time.Duration
|
var expiration time.Duration
|
||||||
if ttl < 0 {
|
if ttl < 0 {
|
||||||
if err = rdb.Persist(ctx, key).Err(); err != nil {
|
if err = client.Persist(ctx, key).Err(); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
expiration = time.Duration(ttl) * time.Second
|
expiration = time.Duration(ttl) * time.Second
|
||||||
if err = rdb.Expire(ctx, key, expiration).Err(); err != nil {
|
if err = client.Expire(ctx, key, expiration).Err(); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1203,7 +1331,7 @@ func (c *connectionService) SetKeyTTL(connName string, db int, k any, ttl int64)
|
||||||
|
|
||||||
// DeleteKey remove redis key
|
// DeleteKey remove redis key
|
||||||
func (c *connectionService) DeleteKey(connName string, db int, k any) (resp types.JSResp) {
|
func (c *connectionService) DeleteKey(connName string, db int, k any) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1213,29 +1341,39 @@ func (c *connectionService) DeleteKey(connName string, db int, k any) (resp type
|
||||||
var deletedKeys []string
|
var deletedKeys []string
|
||||||
if strings.HasSuffix(key, "*") {
|
if strings.HasSuffix(key, "*") {
|
||||||
// delete by prefix
|
// delete by prefix
|
||||||
var cursor uint64
|
var mutex sync.Mutex
|
||||||
for {
|
del := func(ctx context.Context, cli redis.UniversalClient) error {
|
||||||
var loadedKey []string
|
iter := cli.Scan(ctx, 0, key, 10000).Iterator()
|
||||||
if loadedKey, cursor, err = rdb.Scan(ctx, cursor, key, 10000).Result(); err != nil {
|
for iter.Next(ctx) {
|
||||||
resp.Msg = err.Error()
|
subKey := iter.Val()
|
||||||
return
|
if err = cli.Unlink(ctx, subKey).Err(); err != nil {
|
||||||
|
log.Println("unlink error", err.Error())
|
||||||
|
return err
|
||||||
} else {
|
} else {
|
||||||
if err = rdb.Del(ctx, loadedKey...).Err(); err != nil {
|
mutex.Lock()
|
||||||
resp.Msg = err.Error()
|
deletedKeys = append(deletedKeys, subKey)
|
||||||
return
|
mutex.Unlock()
|
||||||
} else {
|
|
||||||
deletedKeys = append(deletedKeys, loadedKey...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// no more loadedKey
|
if cluster, ok := client.(*redis.ClusterClient); ok {
|
||||||
if cursor == 0 {
|
// cluster mode
|
||||||
break
|
err = cluster.ForEachMaster(ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
|
return del(ctx, cli)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
err = del(ctx, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// delete key only
|
// delete key only
|
||||||
_, err = rdb.Del(ctx, key).Result()
|
_, err = client.Del(ctx, key).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -1252,14 +1390,18 @@ func (c *connectionService) DeleteKey(connName string, db int, k any) (resp type
|
||||||
|
|
||||||
// RenameKey rename key
|
// RenameKey rename key
|
||||||
func (c *connectionService) RenameKey(connName string, db int, key, newKey string) (resp types.JSResp) {
|
func (c *connectionService) RenameKey(connName string, db int, key, newKey string) (resp types.JSResp) {
|
||||||
rdb, ctx, err := c.getRedisClient(connName, db)
|
client, ctx, err := c.getRedisClient(connName, db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = rdb.RenameNX(ctx, key, newKey).Result()
|
if _, ok := client.(*redis.ClusterClient); ok {
|
||||||
if err != nil {
|
resp.Msg = "RENAME not support in cluster mode yet"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = client.RenameNX(ctx, key, newKey).Result(); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,11 +113,13 @@ func (p *preferencesService) GetAppVersion() (resp types.JSResp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *preferencesService) SaveWindowSize(width, height int) {
|
func (p *preferencesService) SaveWindowSize(width, height int) {
|
||||||
|
if width >= consts.DEFAULT_WINDOW_WIDTH && height >= consts.DEFAULT_WINDOW_HEIGHT {
|
||||||
p.UpdatePreferences(map[string]any{
|
p.UpdatePreferences(map[string]any{
|
||||||
"behavior.windowWidth": width,
|
"behavior.windowWidth": width,
|
||||||
"behavior.windowHeight": height,
|
"behavior.windowHeight": height,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *preferencesService) GetWindowSize() (width, height int) {
|
func (p *preferencesService) GetWindowSize() (width, height int) {
|
||||||
data := p.pref.GetPreferences()
|
data := p.pref.GetPreferences()
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"tinyrdm/backend/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type systemService struct {
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
var system *systemService
|
||||||
|
var onceSystem sync.Once
|
||||||
|
|
||||||
|
func System() *systemService {
|
||||||
|
if system == nil {
|
||||||
|
onceSystem.Do(func() {
|
||||||
|
system = &systemService{}
|
||||||
|
go system.loopWindowEvent()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return system
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemService) Start(ctx context.Context) {
|
||||||
|
s.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectFile open file dialog to select a file
|
||||||
|
func (s *systemService) SelectFile(title string) (resp types.JSResp) {
|
||||||
|
filepath, err := runtime.OpenFileDialog(s.ctx, runtime.OpenDialogOptions{
|
||||||
|
Title: title,
|
||||||
|
ShowHiddenFiles: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
resp.Msg = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Success = true
|
||||||
|
resp.Data = map[string]any{
|
||||||
|
"path": filepath,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemService) loopWindowEvent() {
|
||||||
|
var fullscreen, maximised, minimised, normal bool
|
||||||
|
var width, height int
|
||||||
|
var dirty bool
|
||||||
|
for {
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
if s.ctx == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty = false
|
||||||
|
if f := runtime.WindowIsFullscreen(s.ctx); f != fullscreen {
|
||||||
|
// full-screen switched
|
||||||
|
fullscreen = f
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if w, h := runtime.WindowGetSize(s.ctx); w != width || h != height {
|
||||||
|
// window size changed
|
||||||
|
width, height = w, h
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if m := runtime.WindowIsMaximised(s.ctx); m != maximised {
|
||||||
|
maximised = m
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if m := runtime.WindowIsMinimised(s.ctx); m != minimised {
|
||||||
|
minimised = m
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := runtime.WindowIsNormal(s.ctx); n != normal {
|
||||||
|
normal = n
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dirty {
|
||||||
|
runtime.EventsEmit(s.ctx, "window_changed", map[string]any{
|
||||||
|
"fullscreen": fullscreen,
|
||||||
|
"width": width,
|
||||||
|
"height": height,
|
||||||
|
"maximised": maximised,
|
||||||
|
"minimised": minimised,
|
||||||
|
"normal": normal,
|
||||||
|
})
|
||||||
|
|
||||||
|
if !fullscreen && !minimised {
|
||||||
|
// save window size
|
||||||
|
Preferences().SaveWindowSize(width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,8 +16,10 @@ type ConnectionConfig struct {
|
||||||
DBFilterType string `json:"dbFilterType" yaml:"db_filter_type,omitempty"`
|
DBFilterType string `json:"dbFilterType" yaml:"db_filter_type,omitempty"`
|
||||||
DBFilterList []int `json:"dbFilterList" yaml:"db_filter_list,omitempty"`
|
DBFilterList []int `json:"dbFilterList" yaml:"db_filter_list,omitempty"`
|
||||||
MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"`
|
MarkColor string `json:"markColor,omitempty" yaml:"mark_color,omitempty"`
|
||||||
|
SSL ConnectionSSL `json:"ssl,omitempty" yaml:"ssl,omitempty"`
|
||||||
SSH ConnectionSSH `json:"ssh,omitempty" yaml:"ssh,omitempty"`
|
SSH ConnectionSSH `json:"ssh,omitempty" yaml:"ssh,omitempty"`
|
||||||
Sentinel ConnectionSentinel `json:"sentinel,omitempty" yaml:"sentinel,omitempty"`
|
Sentinel ConnectionSentinel `json:"sentinel,omitempty" yaml:"sentinel,omitempty"`
|
||||||
|
Cluster ConnectionCluster `json:"cluster,omitempty" yaml:"cluster,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
|
@ -28,11 +30,6 @@ type Connection struct {
|
||||||
|
|
||||||
type Connections []Connection
|
type Connections []Connection
|
||||||
|
|
||||||
type ConnectionGroup struct {
|
|
||||||
GroupName string `json:"groupName" yaml:"group_name"`
|
|
||||||
Connections []Connection `json:"connections" yaml:"connections"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionDB struct {
|
type ConnectionDB struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
|
@ -41,20 +38,31 @@ type ConnectionDB struct {
|
||||||
AvgTTL int `json:"avgTtl,omitempty"`
|
AvgTTL int `json:"avgTtl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionSSL struct {
|
||||||
|
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"`
|
||||||
|
KeyFile string `json:"keyFile,omitempty" yaml:"keyFile,omitempty"`
|
||||||
|
CertFile string `json:"certFile,omitempty" yaml:"certFile,omitempty"`
|
||||||
|
CAFile string `json:"caFile,omitempty" yaml:"caFile,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ConnectionSSH struct {
|
type ConnectionSSH struct {
|
||||||
Enable bool `json:"enable" yaml:"enable"`
|
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"`
|
||||||
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,omitempty" yaml:"login_type"`
|
||||||
Username string `json:"username" 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"`
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectionSentinel struct {
|
type ConnectionSentinel struct {
|
||||||
Enable bool `json:"enable" yaml:"enable"`
|
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"`
|
||||||
Master string `json:"master" yaml:"master"`
|
Master string `json:"master,omitempty" yaml:"master,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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionCluster struct {
|
||||||
|
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty"`
|
||||||
|
}
|
||||||
|
|
|
@ -76,10 +76,8 @@ func (l *LogHook) DialHook(next redis.DialHook) redis.DialHook {
|
||||||
|
|
||||||
func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||||
log.Println(cmd)
|
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
err := next(ctx, cmd)
|
err := next(ctx, cmd)
|
||||||
if l.cmdExec != nil {
|
|
||||||
b := make([]byte, 0, 64)
|
b := make([]byte, 0, 64)
|
||||||
for i, arg := range cmd.Args() {
|
for i, arg := range cmd.Args() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
@ -87,6 +85,8 @@ func (l *LogHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||||
}
|
}
|
||||||
b = appendArg(b, arg)
|
b = appendArg(b, arg)
|
||||||
}
|
}
|
||||||
|
log.Println(string(b))
|
||||||
|
if l.cmdExec != nil {
|
||||||
l.cmdExec(string(b), time.Since(t).Milliseconds())
|
l.cmdExec(string(b), time.Since(t).Milliseconds())
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -98,19 +98,22 @@ func (l *LogHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.Proc
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
err := next(ctx, cmds)
|
err := next(ctx, cmds)
|
||||||
cost := time.Since(t).Milliseconds()
|
cost := time.Since(t).Milliseconds()
|
||||||
|
b := make([]byte, 0, 64)
|
||||||
for _, cmd := range cmds {
|
for _, cmd := range cmds {
|
||||||
log.Println("pipeline: ", cmd)
|
log.Println("pipeline: ", cmd)
|
||||||
if l.cmdExec != nil {
|
if l.cmdExec != nil {
|
||||||
b := make([]byte, 0, 64)
|
|
||||||
for i, arg := range cmd.Args() {
|
for i, arg := range cmd.Args() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
b = append(b, ' ')
|
b = append(b, ' ')
|
||||||
}
|
}
|
||||||
b = appendArg(b, arg)
|
b = appendArg(b, arg)
|
||||||
}
|
}
|
||||||
l.cmdExec(string(b), cost)
|
b = append(b, '\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if l.cmdExec != nil {
|
||||||
|
l.cmdExec(string(b), cost)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"tinyrdm/backend/types"
|
"tinyrdm/backend/types"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConvertTo convert string to specified type
|
// ConvertTo convert string to specified type
|
||||||
|
@ -187,7 +188,9 @@ func decodeJson(str string) (string, bool) {
|
||||||
|
|
||||||
func decodeBase64(str string) (string, bool) {
|
func decodeBase64(str string) (string, bool) {
|
||||||
if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil {
|
if decodedStr, err := base64.StdEncoding.DecodeString(str); err == nil {
|
||||||
return string(decodedStr), true
|
if s := string(decodedStr); utf8.ValidString(s) {
|
||||||
|
return s, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return str, false
|
return str, false
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.9.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.7",
|
||||||
"sass": "^1.69.0",
|
"sass": "^1.69.3",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.5.0"
|
"vue-i18n": "^9.5.0"
|
||||||
},
|
},
|
||||||
|
@ -1048,9 +1048,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/highlight.js": {
|
"node_modules/highlight.js": {
|
||||||
"version": "11.8.0",
|
"version": "11.9.0",
|
||||||
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.8.0.tgz",
|
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz",
|
||||||
"integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==",
|
"integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
|
@ -1393,9 +1393,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pinia": {
|
"node_modules/pinia": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz",
|
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
|
||||||
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
|
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-api": "^6.5.0",
|
"@vue/devtools-api": "^6.5.0",
|
||||||
"vue-demi": ">=0.14.5"
|
"vue-demi": ">=0.14.5"
|
||||||
|
@ -1545,9 +1545,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.69.0",
|
"version": "1.69.3",
|
||||||
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.0.tgz",
|
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.3.tgz",
|
||||||
"integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
|
"integrity": "sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": ">=3.0.0 <4.0.0",
|
"chokidar": ">=3.0.0 <4.0.0",
|
||||||
"immutable": "^4.0.0",
|
"immutable": "^4.0.0",
|
||||||
|
@ -2656,9 +2656,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"highlight.js": {
|
"highlight.js": {
|
||||||
"version": "11.8.0",
|
"version": "11.9.0",
|
||||||
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.8.0.tgz",
|
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz",
|
||||||
"integrity": "sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg=="
|
"integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw=="
|
||||||
},
|
},
|
||||||
"human-signals": {
|
"human-signals": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
@ -2926,9 +2926,9 @@
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||||
},
|
},
|
||||||
"pinia": {
|
"pinia": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz",
|
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.7.tgz",
|
||||||
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
|
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/devtools-api": "^6.5.0",
|
"@vue/devtools-api": "^6.5.0",
|
||||||
"vue-demi": ">=0.14.5"
|
"vue-demi": ">=0.14.5"
|
||||||
|
@ -3025,9 +3025,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sass": {
|
"sass": {
|
||||||
"version": "1.69.0",
|
"version": "1.69.3",
|
||||||
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.0.tgz",
|
"resolved": "https://registry.npmmirror.com/sass/-/sass-1.69.3.tgz",
|
||||||
"integrity": "sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==",
|
"integrity": "sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chokidar": ">=3.0.0 <4.0.0",
|
"chokidar": ">=3.0.0 <4.0.0",
|
||||||
"immutable": "^4.0.0",
|
"immutable": "^4.0.0",
|
||||||
|
|
|
@ -10,10 +10,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.9.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.7",
|
||||||
"sass": "^1.69.0",
|
"sass": "^1.69.3",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.5.0"
|
"vue-i18n": "^9.5.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
82f42b67ae979cb1af64c05c79c5251f
|
3b7cabd69c1c3dad11dea0682b3c6bef
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import ContentPane from './components/content/ContentPane.vue'
|
import ContentPane from './components/content/ContentPane.vue'
|
||||||
import BrowserPane from './components/sidebar/BrowserPane.vue'
|
import BrowserPane from './components/sidebar/BrowserPane.vue'
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { debounce, get } from 'lodash'
|
import { debounce, get } from 'lodash'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
import NavMenu from './components/sidebar/NavMenu.vue'
|
import NavMenu from './components/sidebar/NavMenu.vue'
|
||||||
|
@ -13,7 +13,7 @@ import useConnectionStore from './stores/connections.js'
|
||||||
import ContentLogPane from './components/content/ContentLogPane.vue'
|
import ContentLogPane from './components/content/ContentLogPane.vue'
|
||||||
import ContentValueTab from '@/components/content/ContentValueTab.vue'
|
import ContentValueTab from '@/components/content/ContentValueTab.vue'
|
||||||
import ToolbarControlWidget from '@/components/common/ToolbarControlWidget.vue'
|
import ToolbarControlWidget from '@/components/common/ToolbarControlWidget.vue'
|
||||||
import { WindowIsFullscreen, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
|
import { EventsOn, WindowIsFullscreen, WindowIsMaximised, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
|
||||||
import { isMacOS } from '@/utils/platform.js'
|
import { isMacOS } from '@/utils/platform.js'
|
||||||
import iconUrl from '@/assets/images/icon.png'
|
import iconUrl from '@/assets/images/icon.png'
|
||||||
|
|
||||||
|
@ -37,11 +37,11 @@ const logPaneRef = ref(null)
|
||||||
// const preferences = ref({})
|
// const preferences = ref({})
|
||||||
// provide('preferences', preferences)
|
// provide('preferences', preferences)
|
||||||
|
|
||||||
const saveWidth = debounce(prefStore.savePreferences, 1000, { trailing: true })
|
const saveSidebarWidth = debounce(prefStore.savePreferences, 1000, { trailing: true })
|
||||||
const handleResize = (evt) => {
|
const handleResize = (evt) => {
|
||||||
if (data.resizing) {
|
if (data.resizing) {
|
||||||
prefStore.setAsideWidth(Math.max(evt.clientX - data.navMenuWidth, 300))
|
prefStore.setAsideWidth(Math.max(evt.clientX - data.navMenuWidth, 300))
|
||||||
saveWidth()
|
saveSidebarWidth()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,6 @@ const stopResize = () => {
|
||||||
data.resizing = false
|
data.resizing = false
|
||||||
document.removeEventListener('mousemove', handleResize)
|
document.removeEventListener('mousemove', handleResize)
|
||||||
document.removeEventListener('mouseup', stopResize)
|
document.removeEventListener('mouseup', stopResize)
|
||||||
// TODO: Save sidebar x-position
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const startResize = () => {
|
const startResize = () => {
|
||||||
|
@ -75,20 +74,54 @@ watch(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const borderRadius = computed(() => {
|
|
||||||
// FIXME: cannot get full screen status sync?
|
|
||||||
// if (isMacOS()) {
|
|
||||||
// return WindowIsFullscreen().then((full) => {
|
|
||||||
// return full ? '0' : '10px'
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
return '10px'
|
|
||||||
})
|
|
||||||
|
|
||||||
const border = computed(() => {
|
const border = computed(() => {
|
||||||
const color = isMacOS() && false ? '#0000' : themeVars.value.borderColor
|
const color = isMacOS() && false ? '#0000' : themeVars.value.borderColor
|
||||||
return `1px solid ${color}`
|
return `1px solid ${color}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const borderRadius = ref(10)
|
||||||
|
const logoPaddingLeft = ref(10)
|
||||||
|
const maximised = ref(false)
|
||||||
|
const toggleWindowRadius = (on) => {
|
||||||
|
borderRadius.value = on ? 10 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const onToggleFullscreen = (fullscreen) => {
|
||||||
|
if (fullscreen) {
|
||||||
|
logoPaddingLeft.value = 10
|
||||||
|
toggleWindowRadius(false)
|
||||||
|
} else {
|
||||||
|
logoPaddingLeft.value = isMacOS() ? 70 : 10
|
||||||
|
toggleWindowRadius(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onToggleMaximize = (isMaximised) => {
|
||||||
|
if (isMaximised) {
|
||||||
|
maximised.value = true
|
||||||
|
if (!isMacOS()) {
|
||||||
|
toggleWindowRadius(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maximised.value = false
|
||||||
|
if (!isMacOS()) {
|
||||||
|
toggleWindowRadius(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventsOn('window_changed', (info) => {
|
||||||
|
const { fullscreen, maximised } = info
|
||||||
|
onToggleFullscreen(fullscreen === true)
|
||||||
|
onToggleMaximize(maximised)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const fullscreen = await WindowIsFullscreen()
|
||||||
|
onToggleFullscreen(fullscreen === true)
|
||||||
|
const maximised = await WindowIsMaximised()
|
||||||
|
onToggleMaximize(maximised)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -96,7 +129,7 @@ const border = computed(() => {
|
||||||
<n-spin
|
<n-spin
|
||||||
:show="props.loading"
|
:show="props.loading"
|
||||||
:theme-overrides="{ opacitySpinning: 0 }"
|
:theme-overrides="{ opacitySpinning: 0 }"
|
||||||
:style="{ backgroundColor: themeVars.bodyColor, borderRadius, border }">
|
:style="{ backgroundColor: themeVars.bodyColor, borderRadius: `${borderRadius}px`, border }">
|
||||||
<div
|
<div
|
||||||
id="app-content-wrapper"
|
id="app-content-wrapper"
|
||||||
class="flex-box-v"
|
class="flex-box-v"
|
||||||
|
@ -116,7 +149,7 @@ const border = computed(() => {
|
||||||
id="app-toolbar-title"
|
id="app-toolbar-title"
|
||||||
:style="{
|
:style="{
|
||||||
width: `${data.navMenuWidth + prefStore.behavior.asideWidth - 4}px`,
|
width: `${data.navMenuWidth + prefStore.behavior.asideWidth - 4}px`,
|
||||||
paddingLeft: isMacOS() ? '70px' : '10px',
|
paddingLeft: `${logoPaddingLeft}px`,
|
||||||
}">
|
}">
|
||||||
<n-space align="center" :wrap-item="false" :wrap="false" :size="3">
|
<n-space align="center" :wrap-item="false" :wrap="false" :size="3">
|
||||||
<n-avatar :src="iconUrl" color="#0000" :size="35" style="min-width: 35px" />
|
<n-avatar :src="iconUrl" color="#0000" :size="35" style="min-width: 35px" />
|
||||||
|
@ -143,7 +176,11 @@ const border = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-item-expand"></div>
|
<div class="flex-item-expand"></div>
|
||||||
<!-- simulate window control buttons -->
|
<!-- simulate window control buttons -->
|
||||||
<toolbar-control-widget v-if="!isMacOS()" :size="data.toolbarHeight" style="align-self: flex-start" />
|
<toolbar-control-widget
|
||||||
|
v-if="!isMacOS()"
|
||||||
|
:size="data.toolbarHeight"
|
||||||
|
:maximised="maximised"
|
||||||
|
style="align-self: flex-start" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- content -->
|
<!-- content -->
|
||||||
|
|
|
@ -4,7 +4,8 @@ import WindowMax from '@/components/icons/WindowMax.vue'
|
||||||
import WindowClose from '@/components/icons/WindowClose.vue'
|
import WindowClose from '@/components/icons/WindowClose.vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
import { Quit, WindowIsMaximised, WindowMinimise, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
|
import { Quit, WindowMinimise, WindowToggleMaximise } from 'wailsjs/runtime/runtime.js'
|
||||||
|
import WindowRestore from '@/components/icons/WindowRestore.vue'
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -12,6 +13,9 @@ const props = defineProps({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 35,
|
default: 35,
|
||||||
},
|
},
|
||||||
|
maximised: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const buttonSize = computed(() => {
|
const buttonSize = computed(() => {
|
||||||
|
@ -33,7 +37,7 @@ const handleClose = () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-space :wrap-item="false" align="center" justify="center" :size="0">
|
<n-space :wrap-item="false" align="center" justify="center" :size="0">
|
||||||
<n-tooltip :show-arrow="false">
|
<n-tooltip :show-arrow="false" :delay="1000">
|
||||||
{{ $t('menu.minimise') }}
|
{{ $t('menu.minimise') }}
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="btn-wrapper" @click="handleMinimise">
|
<div class="btn-wrapper" @click="handleMinimise">
|
||||||
|
@ -41,15 +45,23 @@ const handleClose = () => {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<n-tooltip :show-arrow="false">
|
<n-tooltip :show-arrow="false" :delay="1000" v-if="maximised">
|
||||||
{{ WindowIsMaximised() ? $t('menu.restore') : $t('menu.maximise') }}
|
{{ $t('menu.restore') }}
|
||||||
|
<template #trigger>
|
||||||
|
<div class="btn-wrapper" @click="handleMaximise">
|
||||||
|
<window-restore />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-tooltip>
|
||||||
|
<n-tooltip :show-arrow="false" :delay="1000" v-else>
|
||||||
|
{{ $t('menu.maximise') }}
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="btn-wrapper" @click="handleMaximise">
|
<div class="btn-wrapper" @click="handleMaximise">
|
||||||
<window-max />
|
<window-max />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<n-tooltip :show-arrow="false">
|
<n-tooltip :show-arrow="false" :delay="1000">
|
||||||
{{ $t('menu.close') }}
|
{{ $t('menu.close') }}
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<div class="btn-wrapper" @click="handleClose">
|
<div class="btn-wrapper" @click="handleClose">
|
||||||
|
|
|
@ -12,7 +12,7 @@ import useConnectionStore from 'stores/connections.js'
|
||||||
import Copy from '@/components/icons/Copy.vue'
|
import Copy from '@/components/icons/Copy.vue'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty, padStart } from 'lodash'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
@ -51,6 +51,23 @@ const keyName = computed(() => {
|
||||||
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
|
return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const ttlString = computed(() => {
|
||||||
|
let s = ''
|
||||||
|
if (props.ttl > 0) {
|
||||||
|
const hours = Math.floor(props.ttl / 3600)
|
||||||
|
s += padStart(hours + ':', 3, '0')
|
||||||
|
const minutes = Math.floor((props.ttl % 3600) / 60)
|
||||||
|
s += padStart(minutes + ':', 3, '0')
|
||||||
|
const seconds = Math.floor(props.ttl % 60)
|
||||||
|
s += padStart(seconds + '', 2, '0')
|
||||||
|
} else if (props.ttl < 0) {
|
||||||
|
s = i18n.t('interface.forever')
|
||||||
|
} else {
|
||||||
|
s = '00:00:00'
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
|
||||||
const onReloadKey = () => {
|
const onReloadKey = () => {
|
||||||
connectionStore.loadKeyValue(props.server, props.db, keyName.value)
|
connectionStore.loadKeyValue(props.server, props.db, keyName.value)
|
||||||
}
|
}
|
||||||
|
@ -104,13 +121,10 @@ const onDeleteKey = () => {
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="Timer" size="18" />
|
<n-icon :component="Timer" size="18" />
|
||||||
</template>
|
</template>
|
||||||
<template v-if="ttl < 0">
|
{{ ttlString }}
|
||||||
{{ $t('interface.forever') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>{{ ttl }} {{ $t('common.second') }}</template>
|
|
||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
TTL
|
TTL{{ `${ttl > 0 ? ': ' + ttl + $t('common.second') : ''}` }}
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="onRenameKey" />
|
<icon-button :icon="Edit" border size="18" t-tooltip="interface.rename_key" @click="onRenameKey" />
|
||||||
</n-button-group>
|
</n-button-group>
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
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 { ListSentinelMasters, SelectKeyFile, TestConnection } from 'wailsjs/go/services/connectionService.js'
|
import { ListSentinelMasters, 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'
|
||||||
|
import { SelectFile } from 'wailsjs/go/services/systemService.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog for new or edit connection
|
* Dialog for new or edit connection
|
||||||
|
@ -82,12 +83,36 @@ const sshLoginType = computed(() => {
|
||||||
return get(generalForm.value, 'ssh.loginType', 'pwd')
|
return get(generalForm.value, 'ssh.loginType', 'pwd')
|
||||||
})
|
})
|
||||||
|
|
||||||
const onChoosePKFile = async () => {
|
const onSSHChooseKey = async () => {
|
||||||
const { success, data } = await SelectKeyFile(i18n.t('dialogue.connection.ssh.pkfile_selection_title'))
|
const { success, data } = await SelectFile()
|
||||||
|
const path = get(data, 'path', '')
|
||||||
|
if (!isEmpty(path)) {
|
||||||
|
generalForm.value.ssh.pkFile = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSSLChooseCert = async () => {
|
||||||
|
const { success, data } = await SelectFile()
|
||||||
|
const path = get(data, 'path', '')
|
||||||
|
if (!isEmpty(path)) {
|
||||||
|
generalForm.value.ssl.certFile = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSSLChooseKey = async () => {
|
||||||
|
const { success, data } = await SelectFile()
|
||||||
|
const path = get(data, 'path', '')
|
||||||
|
if (!isEmpty(path)) {
|
||||||
|
generalForm.value.ssl.keyFile = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSSLChooseCA = async () => {
|
||||||
|
const { success, data } = await SelectFile()
|
||||||
if (!success) {
|
if (!success) {
|
||||||
generalForm.value.ssh.pkFile = ''
|
generalForm.value.ssl.caFile = ''
|
||||||
} else {
|
} else {
|
||||||
generalForm.value.ssh.pkFile = get(data, 'path', '')
|
generalForm.value.ssl.caFile = get(data, 'path', '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,8 +174,13 @@ const onSaveConnection = async () => {
|
||||||
generalForm.value.dbFilterList = []
|
generalForm.value.dbFilterList = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trim ssl data
|
||||||
|
if (!!!generalForm.value.ssl.enable) {
|
||||||
|
generalForm.value.ssl = {}
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
case 'pkfile':
|
case 'pkfile':
|
||||||
generalForm.value.ssh.password = ''
|
generalForm.value.ssh.password = ''
|
||||||
|
@ -162,15 +192,16 @@ const onSaveConnection = async () => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ssh disabled, reset to default value
|
// ssh disabled, reset to default value
|
||||||
const { ssh } = connectionStore.newDefaultConnection()
|
generalForm.value.ssh = {}
|
||||||
generalForm.value.ssh = ssh
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim sentinel data
|
// trim sentinel data
|
||||||
if (!generalForm.value.sentinel.enable) {
|
if (!!!generalForm.value.sentinel.enable) {
|
||||||
generalForm.value.sentinel.master = ''
|
generalForm.value.sentinel = {}
|
||||||
generalForm.value.sentinel.username = ''
|
}
|
||||||
generalForm.value.sentinel.password = ''
|
|
||||||
|
if (!!!generalForm.value.cluster.enable) {
|
||||||
|
generalForm.value.cluster = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// store new connection
|
// store new connection
|
||||||
|
@ -248,6 +279,7 @@ const onClose = () => {
|
||||||
:show-icon="false"
|
:show-icon="false"
|
||||||
:title="isEditMode ? $t('dialogue.connection.edit_title') : $t('dialogue.connection.new_title')"
|
:title="isEditMode ? $t('dialogue.connection.edit_title') : $t('dialogue.connection.new_title')"
|
||||||
preset="dialog"
|
preset="dialog"
|
||||||
|
style="width: 600px"
|
||||||
transform-origin="center">
|
transform-origin="center">
|
||||||
<n-spin :show="closingConnection">
|
<n-spin :show="closingConnection">
|
||||||
<n-tabs v-model:value="tab" animated type="line">
|
<n-tabs v-model:value="tab" animated type="line">
|
||||||
|
@ -386,6 +418,60 @@ const onClose = () => {
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<!-- SSL pane -->
|
||||||
|
<n-tab-pane :tab="$t('dialogue.connection.ssl.title')" display-directive="show" name="ssl">
|
||||||
|
<n-form-item label-placement="left">
|
||||||
|
<n-checkbox v-model:checked="generalForm.ssl.enable" size="medium">
|
||||||
|
{{ $t('dialogue.connection.ssl.enable') }}
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form
|
||||||
|
:model="generalForm.ssl"
|
||||||
|
:show-require-mark="false"
|
||||||
|
:disabled="!generalForm.ssl.enable"
|
||||||
|
label-placement="top">
|
||||||
|
<n-form-item :label="$t('dialogue.connection.ssl.cert_file')">
|
||||||
|
<n-input-group>
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssl.certFile"
|
||||||
|
:placeholder="$t('dialogue.connection.ssl.cert_file_tip')"
|
||||||
|
clearable />
|
||||||
|
<n-button
|
||||||
|
:focusable="false"
|
||||||
|
:disabled="!generalForm.ssl.enable"
|
||||||
|
@click="onSSLChooseCert">
|
||||||
|
...
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('dialogue.connection.ssl.key_file')">
|
||||||
|
<n-input-group>
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssl.keyFile"
|
||||||
|
:placeholder="$t('dialogue.connection.ssl.key_file_tip')"
|
||||||
|
clearable />
|
||||||
|
<n-button
|
||||||
|
:focusable="false"
|
||||||
|
:disabled="!generalForm.ssl.enable"
|
||||||
|
@click="onSSLChooseKey">
|
||||||
|
...
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="$t('dialogue.connection.ssl.ca_file')">
|
||||||
|
<n-input-group>
|
||||||
|
<n-input
|
||||||
|
v-model:value="generalForm.ssl.caFile"
|
||||||
|
:placeholder="$t('dialogue.connection.ssl.ca_file_tip')"
|
||||||
|
clearable />
|
||||||
|
<n-button :focusable="false" :disabled="!generalForm.ssl.enable" @click="onSSLChooseCA">
|
||||||
|
...
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- SSH pane -->
|
<!-- SSH pane -->
|
||||||
<n-tab-pane :tab="$t('dialogue.connection.ssh.title')" display-directive="show" name="ssh">
|
<n-tab-pane :tab="$t('dialogue.connection.ssh.title')" display-directive="show" name="ssh">
|
||||||
<n-form-item label-placement="left">
|
<n-form-item label-placement="left">
|
||||||
|
@ -434,7 +520,13 @@ const onClose = () => {
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="generalForm.ssh.pkFile"
|
v-model:value="generalForm.ssh.pkFile"
|
||||||
:placeholder="$t('dialogue.connection.ssh.pkfile_tip')" />
|
:placeholder="$t('dialogue.connection.ssh.pkfile_tip')" />
|
||||||
<n-button :focusable="false" @click="onChoosePKFile">...</n-button>
|
<n-button
|
||||||
|
:focusable="false"
|
||||||
|
:disabled="!generalForm.ssh.enable"
|
||||||
|
@click="onSSHChooseKey"
|
||||||
|
clearable>
|
||||||
|
...
|
||||||
|
</n-button>
|
||||||
</n-input-group>
|
</n-input-group>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.ssh.passphrase')">
|
<n-form-item v-if="sshLoginType === 'pkfile'" :label="$t('dialogue.connection.ssh.passphrase')">
|
||||||
|
@ -486,8 +578,20 @@ const onClose = () => {
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
|
||||||
<!-- TODO: SSL tab pane -->
|
<!-- Cluster pane -->
|
||||||
<!-- TODO: Cluster tab pane -->
|
<n-tab-pane :tab="$t('dialogue.connection.cluster.title')" display-directive="show" name="cluster">
|
||||||
|
<n-form-item label-placement="left">
|
||||||
|
<n-checkbox v-model:checked="generalForm.cluster.enable" size="medium">
|
||||||
|
{{ $t('dialogue.connection.cluster.enable') }}
|
||||||
|
</n-checkbox>
|
||||||
|
</n-form-item>
|
||||||
|
<!-- <n-form-->
|
||||||
|
<!-- :model="generalForm.cluster"-->
|
||||||
|
<!-- :show-require-mark="false"-->
|
||||||
|
<!-- :disabled="!generalForm.cluster.enable"-->
|
||||||
|
<!-- label-placement="top">-->
|
||||||
|
<!-- </n-form>-->
|
||||||
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
|
|
||||||
<!-- test result alert-->
|
<!-- test result alert-->
|
||||||
|
|
|
@ -31,6 +31,7 @@ watch(
|
||||||
ttlForm.keyCode = tab.keyCode
|
ttlForm.keyCode = tab.keyCode
|
||||||
if (tab.ttl < 0) {
|
if (tab.ttl < 0) {
|
||||||
// forever
|
// forever
|
||||||
|
ttlForm.ttl = -1
|
||||||
} else {
|
} else {
|
||||||
ttlForm.ttl = tab.ttl
|
ttlForm.ttl = tab.ttl
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
<script setup>
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
strokeWidth: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
fillColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#dc423c',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg v-if="props.modelValue" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="34"
|
||||||
|
width="8"
|
||||||
|
height="8"
|
||||||
|
:fill="props.fillColor"
|
||||||
|
:stroke="props.fillColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<rect
|
||||||
|
x="8"
|
||||||
|
y="6"
|
||||||
|
width="32"
|
||||||
|
height="12"
|
||||||
|
:fill="props.fillColor"
|
||||||
|
:stroke="props.fillColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M24 34V18"
|
||||||
|
:stroke="props.fillColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M8 34V26H40V34"
|
||||||
|
:stroke="props.fillColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<rect
|
||||||
|
x="36"
|
||||||
|
y="34"
|
||||||
|
width="8"
|
||||||
|
height="8"
|
||||||
|
:fill="props.fillColor"
|
||||||
|
:stroke="props.fillColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<rect
|
||||||
|
x="20"
|
||||||
|
y="34"
|
||||||
|
width="8"
|
||||||
|
height="8"
|
||||||
|
:fill="props.fillColor"
|
||||||
|
:stroke="props.fillColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M14 12H16"
|
||||||
|
stroke="#FFF"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
<svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M5 24L43 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M28 4H20C18.8954 4 18 4.89543 18 6V14C18 15.1046 18.8954 16 20 16H28C29.1046 16 30 15.1046 30 14V6C30 4.89543 29.1046 4 28 4Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M16 32H8C6.89543 32 6 32.8954 6 34V42C6 43.1046 6.89543 44 8 44H16C17.1046 44 18 43.1046 18 42V34C18 32.8954 17.1046 32 16 32Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M40 32H32C30.8954 32 30 32.8954 30 34V42C30 43.1046 30.8954 44 32 44H40C41.1046 44 42 43.1046 42 42V34C42 32.8954 41.1046 32 40 32Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M24 24V16"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M36 32V24"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M12 32V24"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
<svg v-else viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect
|
||||||
|
x="4"
|
||||||
|
y="34"
|
||||||
|
width="8"
|
||||||
|
height="8"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<rect
|
||||||
|
x="8"
|
||||||
|
y="6"
|
||||||
|
width="32"
|
||||||
|
height="12"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M24 34V18"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M8 34V26H40V34"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<rect
|
||||||
|
x="36"
|
||||||
|
y="34"
|
||||||
|
width="8"
|
||||||
|
height="8"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<rect
|
||||||
|
x="20"
|
||||||
|
y="34"
|
||||||
|
width="8"
|
||||||
|
height="8"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M14 12H16"
|
||||||
|
stroke="currentColor"
|
||||||
|
:stroke-width="props.strokeWidth"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -2,17 +2,15 @@
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
size: {
|
size: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: 12,
|
default: 14,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg aria-hidden="false" :width="props.size" :height="props.size" viewBox="0 0 12 12">
|
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none">
|
||||||
<polygon
|
<path d="M8 8L40 40" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
fill="currentColor"
|
<path d="M8 40L40 8" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
fill-rule="evenodd"
|
|
||||||
points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"></polygon>
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,18 @@
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
size: {
|
size: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: 12,
|
default: 14,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg aria-hidden="false" :width="props.size" :height="props.size" viewBox="0 0 12 12">
|
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none">
|
||||||
<rect width="9" height="9" x="1.5" y="1.5" fill="none" stroke="currentColor"></rect>
|
<path
|
||||||
|
d="M39 6H9C7.34315 6 6 7.34315 6 9V39C6 40.6569 7.34315 42 9 42H39C40.6569 42 42 40.6569 42 39V9C42 7.34315 40.6569 6 39 6Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
size: {
|
size: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: 12,
|
default: 14,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<svg aria-hidden="false" :width="props.size" :height="props.size" viewBox="0 0 12 12">
|
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none">
|
||||||
<rect fill="currentColor" width="10" height="1" x="1" y="6"></rect>
|
<path d="M8 24L40 24" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
size: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 14,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<svg :width="props.size" :height="props.size" viewBox="0 0 48 48" fill="none">
|
||||||
|
<path
|
||||||
|
d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path
|
||||||
|
d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
|
@ -5,7 +5,7 @@ import { NIcon, NSpace, NTag } from 'naive-ui'
|
||||||
import Key from '@/components/icons/Key.vue'
|
import Key from '@/components/icons/Key.vue'
|
||||||
import Binary from '@/components/icons/Binary.vue'
|
import Binary from '@/components/icons/Binary.vue'
|
||||||
import ToggleDb from '@/components/icons/ToggleDb.vue'
|
import ToggleDb from '@/components/icons/ToggleDb.vue'
|
||||||
import { find, get, includes, indexOf, isEmpty, pull, remove, size } from 'lodash'
|
import { find, get, includes, indexOf, isEmpty, remove, size } from 'lodash'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import Refresh from '@/components/icons/Refresh.vue'
|
import Refresh from '@/components/icons/Refresh.vue'
|
||||||
import CopyLink from '@/components/icons/CopyLink.vue'
|
import CopyLink from '@/components/icons/CopyLink.vue'
|
||||||
|
@ -16,7 +16,6 @@ import Connect from '@/components/icons/Connect.vue'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
import useConnectionStore from 'stores/connections.js'
|
||||||
import ToggleServer from '@/components/icons/ToggleServer.vue'
|
|
||||||
import Unlink from '@/components/icons/Unlink.vue'
|
import Unlink from '@/components/icons/Unlink.vue'
|
||||||
import Filter from '@/components/icons/Filter.vue'
|
import Filter from '@/components/icons/Filter.vue'
|
||||||
import Close from '@/components/icons/Close.vue'
|
import Close from '@/components/icons/Close.vue'
|
||||||
|
@ -348,14 +347,15 @@ const onUpdateSelectedKeys = (keys, options) => {
|
||||||
|
|
||||||
const renderPrefix = ({ option }) => {
|
const renderPrefix = ({ option }) => {
|
||||||
switch (option.type) {
|
switch (option.type) {
|
||||||
case ConnectionType.Server:
|
// case ConnectionType.Server:
|
||||||
return h(
|
// const icon = option.cluster === true ? ToggleCluster : ToggleServer
|
||||||
NIcon,
|
// return h(
|
||||||
{ size: 20 },
|
// NIcon,
|
||||||
{
|
// { size: 20 },
|
||||||
default: () => h(ToggleServer, { modelValue: false }),
|
// {
|
||||||
},
|
// default: () => h(icon, { modelValue: false }),
|
||||||
)
|
// },
|
||||||
|
// )
|
||||||
case ConnectionType.RedisDB:
|
case ConnectionType.RedisDB:
|
||||||
return h(
|
return h(
|
||||||
NIcon,
|
NIcon,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { NIcon, NSpace, NText, useThemeVars } from 'naive-ui'
|
||||||
import { ConnectionType } from '@/consts/connection_type.js'
|
import { ConnectionType } from '@/consts/connection_type.js'
|
||||||
import ToggleFolder from '@/components/icons/ToggleFolder.vue'
|
import ToggleFolder from '@/components/icons/ToggleFolder.vue'
|
||||||
import ToggleServer from '@/components/icons/ToggleServer.vue'
|
import ToggleServer from '@/components/icons/ToggleServer.vue'
|
||||||
|
import ToggleCluster from '@/components/icons/ToggleCluster.vue'
|
||||||
import { debounce, get, includes, indexOf, isEmpty, split } from 'lodash'
|
import { debounce, get, includes, indexOf, isEmpty, split } from 'lodash'
|
||||||
import Config from '@/components/icons/Config.vue'
|
import Config from '@/components/icons/Config.vue'
|
||||||
import Delete from '@/components/icons/Delete.vue'
|
import Delete from '@/components/icons/Delete.vue'
|
||||||
|
@ -193,12 +194,13 @@ const renderPrefix = ({ option }) => {
|
||||||
case ConnectionType.Server:
|
case ConnectionType.Server:
|
||||||
const connected = connectionStore.isConnected(option.name)
|
const connected = connectionStore.isConnected(option.name)
|
||||||
const color = getServerMarkColor(option.name)
|
const color = getServerMarkColor(option.name)
|
||||||
|
const icon = option.cluster === true ? ToggleCluster : ToggleServer
|
||||||
return h(
|
return h(
|
||||||
NIcon,
|
NIcon,
|
||||||
{ size: 20, color: !!!connected ? color : undefined },
|
{ size: 20, color: !!!connected ? color : undefined },
|
||||||
{
|
{
|
||||||
default: () =>
|
default: () =>
|
||||||
h(ToggleServer, {
|
h(icon, {
|
||||||
modelValue: !!connected,
|
modelValue: !!connected,
|
||||||
fillColor: `rgba(220,66,60,${iconTransparency})`,
|
fillColor: `rgba(220,66,60,${iconTransparency})`,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
},
|
},
|
||||||
"ribbon": {
|
"ribbon": {
|
||||||
"server": "Server",
|
"server": "Server",
|
||||||
"browser": "Browser",
|
"browser": "Data Browser",
|
||||||
"log": "Log"
|
"log": "Log"
|
||||||
},
|
},
|
||||||
"dialogue": {
|
"dialogue": {
|
||||||
|
@ -147,6 +147,16 @@
|
||||||
"dbfilter_input_tip": "Press Enter to confirm",
|
"dbfilter_input_tip": "Press Enter to confirm",
|
||||||
"mark_color": "Mark Color"
|
"mark_color": "Mark Color"
|
||||||
},
|
},
|
||||||
|
"ssl": {
|
||||||
|
"title": "SSL/TLS",
|
||||||
|
"enable": "Enable SSL/TLS",
|
||||||
|
"cert_file": "Public Key",
|
||||||
|
"key_file": "Private Key",
|
||||||
|
"ca_file": "Authority",
|
||||||
|
"cert_file_tip":"Public Key File in PEM format(Cert)",
|
||||||
|
"key_file_tip": "Private Key File in PEM format(Key)",
|
||||||
|
"ca_file_tip": "Certificate Authority File in PEM format(CA)"
|
||||||
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"title": "SSH Tunnel",
|
"title": "SSH Tunnel",
|
||||||
"enable": "Enable SSH Tuntel",
|
"enable": "Enable SSH Tuntel",
|
||||||
|
@ -157,8 +167,7 @@
|
||||||
"usr_tip": "SSH Username",
|
"usr_tip": "SSH Username",
|
||||||
"pwd_tip": "SSH Password",
|
"pwd_tip": "SSH Password",
|
||||||
"pkfile_tip": "SSH Private Key File Path",
|
"pkfile_tip": "SSH Private Key File Path",
|
||||||
"passphrase_tip": "(Optional) Passphrase for Private Key",
|
"passphrase_tip": "(Optional) Passphrase for Private Key"
|
||||||
"pkfile_selection_title": "Please Select Private Key File"
|
|
||||||
},
|
},
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"title": "Sentinel",
|
"title": "Sentinel",
|
||||||
|
@ -169,6 +178,11 @@
|
||||||
"username": "Username for Master Node",
|
"username": "Username for Master Node",
|
||||||
"pwd_tip": "(Optional) Authentication 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)"
|
||||||
|
},
|
||||||
|
"cluster": {
|
||||||
|
"title": "Cluster",
|
||||||
|
"enable": "Serve as Cluster Node",
|
||||||
|
"readonly": "Enables read-only commands on slave nodes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
},
|
},
|
||||||
"ribbon": {
|
"ribbon": {
|
||||||
"server": "服务器",
|
"server": "服务器",
|
||||||
"browser": "浏览器",
|
"browser": "数据浏览",
|
||||||
"log": "日志"
|
"log": "日志"
|
||||||
},
|
},
|
||||||
"dialogue": {
|
"dialogue": {
|
||||||
|
@ -147,6 +147,16 @@
|
||||||
"dbfilter_input_tip": "按回车确认",
|
"dbfilter_input_tip": "按回车确认",
|
||||||
"mark_color": "标记颜色"
|
"mark_color": "标记颜色"
|
||||||
},
|
},
|
||||||
|
"ssl": {
|
||||||
|
"title": "SSL/TLS",
|
||||||
|
"enable": "启用SSL",
|
||||||
|
"cert_file": "公钥文件",
|
||||||
|
"key_file": "私钥文件",
|
||||||
|
"ca_file": "授权文件",
|
||||||
|
"cert_file_tip":"PEM格式公钥文件(Cert)",
|
||||||
|
"key_file_tip": "PEM格式私钥文件(Key)",
|
||||||
|
"ca_file_tip": "PEM格式授权文件(CA)"
|
||||||
|
},
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"enable": "启用SSH隧道",
|
"enable": "启用SSH隧道",
|
||||||
"title": "SSH隧道",
|
"title": "SSH隧道",
|
||||||
|
@ -157,8 +167,7 @@
|
||||||
"usr_tip": "SSH登录用户名",
|
"usr_tip": "SSH登录用户名",
|
||||||
"pwd_tip": "SSH登录密码",
|
"pwd_tip": "SSH登录密码",
|
||||||
"pkfile_tip": "SSH私钥文件路径",
|
"pkfile_tip": "SSH私钥文件路径",
|
||||||
"passphrase_tip": "(可选)SSH私钥密码",
|
"passphrase_tip": "(可选)SSH私钥密码"
|
||||||
"pkfile_selection_title": "请选择私钥文件"
|
|
||||||
},
|
},
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"title": "哨兵模式",
|
"title": "哨兵模式",
|
||||||
|
@ -169,6 +178,10 @@
|
||||||
"username": "主节点用户名",
|
"username": "主节点用户名",
|
||||||
"pwd_tip": "(可选)主节点服务授权用户名",
|
"pwd_tip": "(可选)主节点服务授权用户名",
|
||||||
"usr_tip": "(可选)主节点服务授权密码 (Redis > 6.0)"
|
"usr_tip": "(可选)主节点服务授权密码 (Redis > 6.0)"
|
||||||
|
},
|
||||||
|
"cluster": {
|
||||||
|
"title": "集群模式",
|
||||||
|
"enable": "当前为集群节点"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
|
|
|
@ -59,6 +59,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
* @property {string} label display label
|
* @property {string} label display label
|
||||||
* @property {string} name database name
|
* @property {string} name database name
|
||||||
* @property {number} type
|
* @property {number} type
|
||||||
|
* @property {boolean} cluster is cluster node
|
||||||
* @property {ConnectionItem[]} children
|
* @property {ConnectionItem[]} children
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -146,6 +147,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
label: conn.name,
|
label: conn.name,
|
||||||
name: conn.name,
|
name: conn.name,
|
||||||
type: ConnectionType.Server,
|
type: ConnectionType.Server,
|
||||||
|
cluster: get(conn, 'cluster.enable', false),
|
||||||
// isLeaf: false,
|
// isLeaf: false,
|
||||||
})
|
})
|
||||||
profiles[conn.name] = {
|
profiles[conn.name] = {
|
||||||
|
@ -165,6 +167,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
label: item.name,
|
label: item.name,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
type: ConnectionType.Server,
|
type: ConnectionType.Server,
|
||||||
|
cluster: get(item, 'cluster.enable', false),
|
||||||
// isLeaf: false,
|
// isLeaf: false,
|
||||||
})
|
})
|
||||||
profiles[item.name] = {
|
profiles[item.name] = {
|
||||||
|
@ -228,6 +231,12 @@ const useConnectionStore = defineStore('connections', {
|
||||||
dbFilterType: 'none',
|
dbFilterType: 'none',
|
||||||
dbFilterList: [],
|
dbFilterList: [],
|
||||||
markColor: '',
|
markColor: '',
|
||||||
|
ssl: {
|
||||||
|
enable: false,
|
||||||
|
certFile: '',
|
||||||
|
keyFile: '',
|
||||||
|
caFile: '',
|
||||||
|
},
|
||||||
ssh: {
|
ssh: {
|
||||||
enable: false,
|
enable: false,
|
||||||
addr: '',
|
addr: '',
|
||||||
|
@ -244,6 +253,9 @@ const useConnectionStore = defineStore('connections', {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
|
cluster: {
|
||||||
|
enable: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -624,7 +636,7 @@ const useConnectionStore = defineStore('connections', {
|
||||||
}
|
}
|
||||||
// its danger to delete "non-exists" key, just remove from tree view
|
// its danger to delete "non-exists" key, just remove from tree view
|
||||||
await this.deleteKey(server, db, key, true)
|
await this.deleteKey(server, db, key, true)
|
||||||
// TODO: show key not found page?
|
// TODO: show key not found page or check exists on server first?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
main.go
15
main.go
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
"github.com/wailsapp/wails/v2/pkg/options/linux"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||||||
runtime2 "github.com/wailsapp/wails/v2/pkg/runtime"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"tinyrdm/backend/consts"
|
"tinyrdm/backend/consts"
|
||||||
"tinyrdm/backend/services"
|
"tinyrdm/backend/services"
|
||||||
|
@ -26,7 +25,7 @@ var version = "0.0.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create an instance of the app structure
|
// Create an instance of the app structure
|
||||||
app := NewApp()
|
sysSvc := services.System()
|
||||||
connSvc := services.Connection()
|
connSvc := services.Connection()
|
||||||
prefSvc := services.Preferences()
|
prefSvc := services.Preferences()
|
||||||
prefSvc.SetAppVersion(version)
|
prefSvc.SetAppVersion(version)
|
||||||
|
@ -54,22 +53,14 @@ func main() {
|
||||||
},
|
},
|
||||||
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 0},
|
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 0},
|
||||||
OnStartup: func(ctx context.Context) {
|
OnStartup: func(ctx context.Context) {
|
||||||
app.startup(ctx)
|
sysSvc.Start(ctx)
|
||||||
connSvc.Start(ctx)
|
connSvc.Start(ctx)
|
||||||
},
|
},
|
||||||
OnBeforeClose: func(ctx context.Context) (prevent bool) {
|
|
||||||
// save current window size
|
|
||||||
width, height := runtime2.WindowGetSize(ctx)
|
|
||||||
if width > 0 && height > 0 {
|
|
||||||
prefSvc.SaveWindowSize(width, height)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
OnShutdown: func(ctx context.Context) {
|
OnShutdown: func(ctx context.Context) {
|
||||||
connSvc.Stop(ctx)
|
connSvc.Stop(ctx)
|
||||||
},
|
},
|
||||||
Bind: []interface{}{
|
Bind: []interface{}{
|
||||||
app,
|
sysSvc,
|
||||||
connSvc,
|
connSvc,
|
||||||
prefSvc,
|
prefSvc,
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue