Compare commits
No commits in common. "bdfa31e4b6130e88da4a9e5b833bb160327d21ba" and "8c30daec150f1c47ad688da0338c30f3ac8a8052" have entirely different histories.
bdfa31e4b6
...
8c30daec15
|
@ -98,9 +98,9 @@ func (b *browserService) OpenConnection(name string) (resp types.JSResp) {
|
||||||
selConn := Connection().getConnection(name)
|
selConn := Connection().getConnection(name)
|
||||||
// correct last database index
|
// correct last database index
|
||||||
lastDB := selConn.LastDB
|
lastDB := selConn.LastDB
|
||||||
if selConn.DBFilterType == "show" && !slices.Contains(selConn.DBFilterList, lastDB) {
|
if selConn.DBFilterType == "show" && !sliceutil.Contains(selConn.DBFilterList, lastDB) {
|
||||||
lastDB = selConn.DBFilterList[0]
|
lastDB = selConn.DBFilterList[0]
|
||||||
} else if selConn.DBFilterType == "hide" && slices.Contains(selConn.DBFilterList, lastDB) {
|
} else if selConn.DBFilterType == "hide" && sliceutil.Contains(selConn.DBFilterList, lastDB) {
|
||||||
lastDB = selConn.DBFilterList[0]
|
lastDB = selConn.DBFilterList[0]
|
||||||
}
|
}
|
||||||
if lastDB != selConn.LastDB {
|
if lastDB != selConn.LastDB {
|
||||||
|
@ -233,12 +233,13 @@ func (b *browserService) OpenConnection(name string) (resp types.JSResp) {
|
||||||
|
|
||||||
// CloseConnection close redis server connection
|
// CloseConnection close redis server connection
|
||||||
func (b *browserService) CloseConnection(name string) (resp types.JSResp) {
|
func (b *browserService) CloseConnection(name string) (resp types.JSResp) {
|
||||||
if item, ok := b.connMap[name]; ok {
|
item, ok := b.connMap[name]
|
||||||
|
if ok {
|
||||||
delete(b.connMap, name)
|
delete(b.connMap, name)
|
||||||
|
if item.client != nil {
|
||||||
if item.cancelFunc != nil {
|
if item.cancelFunc != nil {
|
||||||
item.cancelFunc()
|
item.cancelFunc()
|
||||||
}
|
}
|
||||||
if item.client != nil {
|
|
||||||
item.client.Close()
|
item.client.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,7 +247,7 @@ func (b *browserService) CloseConnection(name string) (resp types.JSResp) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *browserService) createRedisClient(ctx context.Context, selConn types.ConnectionConfig) (client redis.UniversalClient, err error) {
|
func (b *browserService) createRedisClient(selConn types.ConnectionConfig) (client redis.UniversalClient, err error) {
|
||||||
hook := redis2.NewHook(selConn.Name, func(cmd string, cost int64) {
|
hook := redis2.NewHook(selConn.Name, func(cmd string, cost int64) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
//last := strings.LastIndex(cmd, ":")
|
//last := strings.LastIndex(cmd, ":")
|
||||||
|
@ -267,10 +268,10 @@ func (b *browserService) createRedisClient(ctx context.Context, selConn types.Co
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = client.Do(ctx, "CLIENT", "SETNAME", url.QueryEscape(selConn.Name)).Err()
|
_ = client.Do(b.ctx, "CLIENT", "SETNAME", url.QueryEscape(selConn.Name)).Err()
|
||||||
// add hook to each node in cluster mode
|
// add hook to each node in cluster mode
|
||||||
if cluster, ok := client.(*redis.ClusterClient); ok {
|
if cluster, ok := client.(*redis.ClusterClient); ok {
|
||||||
err = cluster.ForEachShard(ctx, func(ctx context.Context, cli *redis.Client) error {
|
err = cluster.ForEachShard(b.ctx, func(ctx context.Context, cli *redis.Client) error {
|
||||||
cli.AddHook(hook)
|
cli.AddHook(hook)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -282,7 +283,7 @@ func (b *browserService) createRedisClient(ctx context.Context, selConn types.Co
|
||||||
client.AddHook(hook)
|
client.AddHook(hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = client.Ping(ctx).Result(); err != nil && !errors.Is(err, redis.Nil) {
|
if _, err = client.Ping(b.ctx).Result(); err != nil && !errors.Is(err, redis.Nil) {
|
||||||
err = errors.New("can not connect to redis server:" + err.Error())
|
err = errors.New("can not connect to redis server:" + err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -317,18 +318,13 @@ func (b *browserService) getRedisClient(server string, db int) (item *connection
|
||||||
err = fmt.Errorf("no match connection \"%s\"", server)
|
err = fmt.Errorf("no match connection \"%s\"", server)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancelFunc := context.WithCancel(b.ctx)
|
|
||||||
b.connMap[server] = &connectionItem{
|
|
||||||
ctx: ctx,
|
|
||||||
cancelFunc: cancelFunc,
|
|
||||||
}
|
|
||||||
var connConfig = selConn.ConnectionConfig
|
var connConfig = selConn.ConnectionConfig
|
||||||
connConfig.LastDB = db
|
connConfig.LastDB = db
|
||||||
client, err = b.createRedisClient(ctx, connConfig)
|
client, err = b.createRedisClient(connConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ctx, cancelFunc := context.WithCancel(b.ctx)
|
||||||
item = &connectionItem{
|
item = &connectionItem{
|
||||||
client: client,
|
client: client,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
@ -2013,13 +2009,21 @@ func (b *browserService) SetKeyTTL(server string, db int, k any, ttl int64) (res
|
||||||
|
|
||||||
// BatchSetTTL batch set ttl
|
// BatchSetTTL batch set ttl
|
||||||
func (b *browserService) BatchSetTTL(server string, db int, ks []any, ttl int64, serialNo string) (resp types.JSResp) {
|
func (b *browserService) BatchSetTTL(server string, db int, ks []any, ttl int64, serialNo string) (resp types.JSResp) {
|
||||||
item, err := b.getRedisClient(server, db)
|
conf := Connection().getConnection(server)
|
||||||
if err != nil {
|
if conf == nil {
|
||||||
|
resp.Msg = fmt.Sprintf("no connection profile named: %s", server)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var client redis.UniversalClient
|
||||||
|
var err error
|
||||||
|
var connConfig = conf.ConnectionConfig
|
||||||
|
connConfig.LastDB = db
|
||||||
|
if client, err = b.createRedisClient(connConfig); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := item.client
|
|
||||||
ctx, cancelFunc := context.WithCancel(b.ctx)
|
ctx, cancelFunc := context.WithCancel(b.ctx)
|
||||||
|
defer client.Close()
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
//cancelEvent := "ttling:stop:" + serialNo
|
//cancelEvent := "ttling:stop:" + serialNo
|
||||||
|
@ -2043,7 +2047,7 @@ func (b *browserService) BatchSetTTL(server string, db int, ks []any, ttl int64,
|
||||||
//}
|
//}
|
||||||
if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 {
|
if i >= total-1 || time.Now().Sub(startTime).Milliseconds() > 100 {
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
//runtime.EventsEmit(ctx, processEvent, param)
|
//runtime.EventsEmit(b.ctx, processEvent, param)
|
||||||
// do some sleep to prevent blocking the Redis server
|
// do some sleep to prevent blocking the Redis server
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
@ -2213,13 +2217,22 @@ func (b *browserService) DeleteOneKey(server string, db int, k any) (resp types.
|
||||||
|
|
||||||
// DeleteKeys delete keys sync with notification
|
// DeleteKeys delete keys sync with notification
|
||||||
func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo string) (resp types.JSResp) {
|
func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo string) (resp types.JSResp) {
|
||||||
item, err := b.getRedisClient(server, db)
|
// connect a new connection to export keys
|
||||||
if err != nil {
|
conf := Connection().getConnection(server)
|
||||||
|
if conf == nil {
|
||||||
|
resp.Msg = fmt.Sprintf("no connection profile named: %s", server)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var client redis.UniversalClient
|
||||||
|
var err error
|
||||||
|
var connConfig = conf.ConnectionConfig
|
||||||
|
connConfig.LastDB = db
|
||||||
|
if client, err = b.createRedisClient(connConfig); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := item.client
|
|
||||||
ctx, cancelFunc := context.WithCancel(b.ctx)
|
ctx, cancelFunc := context.WithCancel(b.ctx)
|
||||||
|
defer client.Close()
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
cancelEvent := "delete:stop:" + serialNo
|
cancelEvent := "delete:stop:" + serialNo
|
||||||
|
@ -2281,13 +2294,21 @@ func (b *browserService) DeleteKeys(server string, db int, ks []any, serialNo st
|
||||||
|
|
||||||
// DeleteKeysByPattern delete keys by pattern
|
// DeleteKeysByPattern delete keys by pattern
|
||||||
func (b *browserService) DeleteKeysByPattern(server string, db int, pattern string) (resp types.JSResp) {
|
func (b *browserService) DeleteKeysByPattern(server string, db int, pattern string) (resp types.JSResp) {
|
||||||
item, err := b.getRedisClient(server, db)
|
conf := Connection().getConnection(server)
|
||||||
if err != nil {
|
if conf == nil {
|
||||||
|
resp.Msg = fmt.Sprintf("no connection profile named: %s", server)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var client redis.UniversalClient
|
||||||
|
var err error
|
||||||
|
var connConfig = conf.ConnectionConfig
|
||||||
|
connConfig.LastDB = db
|
||||||
|
if client, err = b.createRedisClient(connConfig); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := item.client
|
|
||||||
ctx, cancelFunc := context.WithCancel(b.ctx)
|
ctx, cancelFunc := context.WithCancel(b.ctx)
|
||||||
|
defer client.Close()
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
var ks []any
|
var ks []any
|
||||||
|
@ -2351,13 +2372,22 @@ func (b *browserService) DeleteKeysByPattern(server string, db int, pattern stri
|
||||||
|
|
||||||
// ExportKey export keys
|
// ExportKey export keys
|
||||||
func (b *browserService) ExportKey(server string, db int, ks []any, path string, includeExpire bool) (resp types.JSResp) {
|
func (b *browserService) ExportKey(server string, db int, ks []any, path string, includeExpire bool) (resp types.JSResp) {
|
||||||
item, err := b.getRedisClient(server, db)
|
// connect a new connection to export keys
|
||||||
if err != nil {
|
conf := Connection().getConnection(server)
|
||||||
|
if conf == nil {
|
||||||
|
resp.Msg = fmt.Sprintf("no connection profile named: %s", server)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var client redis.UniversalClient
|
||||||
|
var err error
|
||||||
|
var connConfig = conf.ConnectionConfig
|
||||||
|
connConfig.LastDB = db
|
||||||
|
if client, err = b.createRedisClient(connConfig); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := item.client
|
|
||||||
ctx, cancelFunc := context.WithCancel(b.ctx)
|
ctx, cancelFunc := context.WithCancel(b.ctx)
|
||||||
|
defer client.Close()
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
file, err := os.Create(path)
|
file, err := os.Create(path)
|
||||||
|
@ -2426,13 +2456,22 @@ func (b *browserService) ExportKey(server string, db int, ks []any, path string,
|
||||||
|
|
||||||
// ImportCSV import data from csv file
|
// ImportCSV import data from csv file
|
||||||
func (b *browserService) ImportCSV(server string, db int, path string, conflict int, ttl int64) (resp types.JSResp) {
|
func (b *browserService) ImportCSV(server string, db int, path string, conflict int, ttl int64) (resp types.JSResp) {
|
||||||
item, err := b.getRedisClient(server, db)
|
// connect a new connection to export keys
|
||||||
if err != nil {
|
conf := Connection().getConnection(server)
|
||||||
|
if conf == nil {
|
||||||
|
resp.Msg = fmt.Sprintf("no connection profile named: %s", server)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var client redis.UniversalClient
|
||||||
|
var err error
|
||||||
|
var connConfig = conf.ConnectionConfig
|
||||||
|
connConfig.LastDB = db
|
||||||
|
if client, err = b.createRedisClient(connConfig); err != nil {
|
||||||
resp.Msg = err.Error()
|
resp.Msg = err.Error()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := item.client
|
|
||||||
ctx, cancelFunc := context.WithCancel(b.ctx)
|
ctx, cancelFunc := context.WithCancel(b.ctx)
|
||||||
|
defer client.Close()
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
|
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
@ -2514,7 +2553,7 @@ func (b *browserService) ImportCSV(server string, db int, path string, conflict
|
||||||
"ignored": ignored,
|
"ignored": ignored,
|
||||||
//"processing": string(key),
|
//"processing": string(key),
|
||||||
}
|
}
|
||||||
runtime.EventsEmit(ctx, processEvent, param)
|
runtime.EventsEmit(b.ctx, processEvent, param)
|
||||||
// do some sleep to prevent blocking the Redis server
|
// do some sleep to prevent blocking the Redis server
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@ package storage
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"slices"
|
|
||||||
"sync"
|
"sync"
|
||||||
"tinyrdm/backend/consts"
|
"tinyrdm/backend/consts"
|
||||||
"tinyrdm/backend/types"
|
"tinyrdm/backend/types"
|
||||||
|
sliceutil "tinyrdm/backend/utils/slice"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConnectionsStorage struct {
|
type ConnectionsStorage struct {
|
||||||
|
@ -256,10 +256,10 @@ func (c *ConnectionsStorage) SaveSortedConnection(sortedConns types.Connections)
|
||||||
|
|
||||||
conns := c.GetConnectionsFlat()
|
conns := c.GetConnectionsFlat()
|
||||||
takeConn := func(name string) (types.Connection, bool) {
|
takeConn := func(name string) (types.Connection, bool) {
|
||||||
idx := slices.IndexFunc(conns, func(connection types.Connection) bool {
|
idx, ok := sliceutil.Find(conns, func(i int) bool {
|
||||||
return connection.Name == name
|
return conns[i].Name == name
|
||||||
})
|
})
|
||||||
if idx >= 0 {
|
if ok {
|
||||||
ret := conns[idx]
|
ret := conns[idx]
|
||||||
conns = append(conns[:idx], conns[idx+1:]...)
|
conns = append(conns[:idx], conns[idx+1:]...)
|
||||||
return ret, true
|
return ret, true
|
||||||
|
|
|
@ -1,11 +1,137 @@
|
||||||
package sliceutil
|
package sliceutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
. "tinyrdm/backend/utils"
|
. "tinyrdm/backend/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Map map items to new array
|
// Get 获取指定索引的值, 如果不存在则返回默认值
|
||||||
|
func Get[S ~[]T, T any](arr S, index int, defaultVal T) T {
|
||||||
|
if index < 0 || index >= len(arr) {
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
return arr[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove 删除指定索引的元素
|
||||||
|
func Remove[S ~[]T, T any](arr S, index int) S {
|
||||||
|
return append(arr[:index], arr[index+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveIf 移除指定条件的元素
|
||||||
|
func RemoveIf[S ~[]T, T any](arr S, cond func(T) bool) S {
|
||||||
|
l := len(arr)
|
||||||
|
if l <= 0 {
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
for i := l - 1; i >= 0; i-- {
|
||||||
|
if cond(arr[i]) {
|
||||||
|
arr = append(arr[:i], arr[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRange 删除从[from, to]部分元素
|
||||||
|
func RemoveRange[S ~[]T, T any](arr S, from, to int) S {
|
||||||
|
return append(arr[:from], arr[to:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find 查找指定条件的元素第一个出现位置
|
||||||
|
func Find[S ~[]T, T any](arr S, matchFunc func(int) bool) (int, bool) {
|
||||||
|
total := len(arr)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
if matchFunc(i) {
|
||||||
|
return i, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyMatch 判断是否有任意元素符合条件
|
||||||
|
func AnyMatch[S ~[]T, T any](arr S, matchFunc func(int) bool) bool {
|
||||||
|
total := len(arr)
|
||||||
|
if total > 0 {
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
if matchFunc(i) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllMatch 判断是否所有元素都符合条件
|
||||||
|
func AllMatch[S ~[]T, T any](arr S, matchFunc func(int) bool) bool {
|
||||||
|
total := len(arr)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
if !matchFunc(i) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals 比较两个切片内容是否完全一致
|
||||||
|
func Equals[S ~[]T, T comparable](arr1, arr2 S) bool {
|
||||||
|
if &arr1 == &arr2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
len1, len2 := len(arr1), len(arr2)
|
||||||
|
if len1 != len2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len1; i++ {
|
||||||
|
if arr1[i] != arr2[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains 判断数组是否包含指定元素
|
||||||
|
func Contains[S ~[]T, T Hashable](arr S, elem T) bool {
|
||||||
|
return AnyMatch(arr, func(idx int) bool {
|
||||||
|
return arr[idx] == elem
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsAny 判断数组是否包含任意指定元素
|
||||||
|
func ContainsAny[S ~[]T, T Hashable](arr S, elems ...T) bool {
|
||||||
|
for _, elem := range elems {
|
||||||
|
if Contains(arr, elem) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsAll 判断数组是否包含所有指定元素
|
||||||
|
func ContainsAll[S ~[]T, T Hashable](arr S, elems ...T) bool {
|
||||||
|
for _, elem := range elems {
|
||||||
|
if !Contains(arr, elem) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter 筛选出符合指定条件的所有元素
|
||||||
|
func Filter[S ~[]T, T any](arr S, filterFunc func(int) bool) []T {
|
||||||
|
total := len(arr)
|
||||||
|
var result []T
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
if filterFunc(i) {
|
||||||
|
result = append(result, arr[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map 数组映射转换
|
||||||
func Map[S ~[]T, T any, R any](arr S, mappingFunc func(int) R) []R {
|
func Map[S ~[]T, T any, R any](arr S, mappingFunc func(int) R) []R {
|
||||||
total := len(arr)
|
total := len(arr)
|
||||||
result := make([]R, total)
|
result := make([]R, total)
|
||||||
|
@ -15,7 +141,7 @@ func Map[S ~[]T, T any, R any](arr S, mappingFunc func(int) R) []R {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterMap filter and map items to new array
|
// FilterMap 数组过滤和映射转换
|
||||||
func FilterMap[S ~[]T, T any, R any](arr S, mappingFunc func(int) (R, bool)) []R {
|
func FilterMap[S ~[]T, T any, R any](arr S, mappingFunc func(int) (R, bool)) []R {
|
||||||
total := len(arr)
|
total := len(arr)
|
||||||
result := make([]R, 0, total)
|
result := make([]R, 0, total)
|
||||||
|
@ -29,7 +155,68 @@ func FilterMap[S ~[]T, T any, R any](arr S, mappingFunc func(int) (R, bool)) []R
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join join any array to a single string by custom function
|
// ToMap 数组转键值对
|
||||||
|
func ToMap[S ~[]T, T any, K Hashable, V any](arr S, mappingFunc func(int) (K, V)) map[K]V {
|
||||||
|
total := len(arr)
|
||||||
|
result := map[K]V{}
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
key, val := mappingFunc(i)
|
||||||
|
result[key] = val
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flat 二维数组扁平化
|
||||||
|
func Flat[T any](arr [][]T) []T {
|
||||||
|
total := len(arr)
|
||||||
|
var result []T
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
subTotal := len(arr[i])
|
||||||
|
for j := 0; j < subTotal; j++ {
|
||||||
|
result = append(result, arr[i][j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlatMap 二维数组扁平化映射
|
||||||
|
func FlatMap[T any, R any](arr [][]T, mappingFunc func(int, int) R) []R {
|
||||||
|
total := len(arr)
|
||||||
|
var result []R
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
subTotal := len(arr[i])
|
||||||
|
for j := 0; j < subTotal; j++ {
|
||||||
|
result = append(result, mappingFunc(i, j))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlatValueMap[T Hashable](arr [][]T) []T {
|
||||||
|
return FlatMap(arr, func(i, j int) T {
|
||||||
|
return arr[i][j]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce 数组累计
|
||||||
|
func Reduce[S ~[]T, T any, R any](arr S, init R, reduceFunc func(R, T) R) R {
|
||||||
|
result := init
|
||||||
|
for _, item := range arr {
|
||||||
|
result = reduceFunc(result, item)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse 反转数组(会修改原数组)
|
||||||
|
func Reverse[S ~[]T, T any](arr S) S {
|
||||||
|
total := len(arr)
|
||||||
|
for i := 0; i < total/2; i++ {
|
||||||
|
arr[i], arr[total-i-1] = arr[total-i-1], arr[i]
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join 数组拼接转字符串
|
||||||
func Join[S ~[]T, T any](arr S, sep string, toStringFunc func(int) string) string {
|
func Join[S ~[]T, T any](arr S, sep string, toStringFunc func(int) string) string {
|
||||||
total := len(arr)
|
total := len(arr)
|
||||||
if total <= 0 {
|
if total <= 0 {
|
||||||
|
@ -49,14 +236,21 @@ func Join[S ~[]T, T any](arr S, sep string, toStringFunc func(int) string) strin
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinString join string array to a single string
|
// JoinString 字符串数组拼接成字符串
|
||||||
func JoinString(arr []string, sep string) string {
|
func JoinString(arr []string, sep string) string {
|
||||||
return Join(arr, sep, func(idx int) string {
|
return Join(arr, sep, func(idx int) string {
|
||||||
return arr[idx]
|
return arr[idx]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unique filter unique item
|
// JoinInt 整形数组拼接转字符串
|
||||||
|
func JoinInt(arr []int, sep string) string {
|
||||||
|
return Join(arr, sep, func(idx int) string {
|
||||||
|
return strconv.Itoa(arr[idx])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unique 数组去重
|
||||||
func Unique[S ~[]T, T Hashable](arr S) S {
|
func Unique[S ~[]T, T Hashable](arr S) S {
|
||||||
result := make(S, 0, len(arr))
|
result := make(S, 0, len(arr))
|
||||||
uniKeys := map[T]struct{}{}
|
uniKeys := map[T]struct{}{}
|
||||||
|
@ -69,3 +263,136 @@ func Unique[S ~[]T, T Hashable](arr S) S {
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UniqueEx 数组去重(任意类型)
|
||||||
|
// @param toKeyFunc 数组元素转为唯一标识字符串函数, 如转为哈希值等
|
||||||
|
func UniqueEx[S ~[]T, T any](arr S, toKeyFunc func(i int) string) S {
|
||||||
|
result := make(S, 0, len(arr))
|
||||||
|
keyArr := Map(arr, toKeyFunc)
|
||||||
|
uniKeys := map[string]struct{}{}
|
||||||
|
var exists bool
|
||||||
|
for i, item := range arr {
|
||||||
|
if _, exists = uniKeys[keyArr[i]]; !exists {
|
||||||
|
uniKeys[keyArr[i]] = struct{}{}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort 顺序排序(会修改原数组)
|
||||||
|
func Sort[S ~[]T, T Hashable](arr S) S {
|
||||||
|
sort.Slice(arr, func(i, j int) bool {
|
||||||
|
return arr[i] <= arr[j]
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortDesc 倒序排序(会修改原数组)
|
||||||
|
func SortDesc[S ~[]T, T Hashable](arr S) S {
|
||||||
|
sort.Slice(arr, func(i, j int) bool {
|
||||||
|
return arr[i] > arr[j]
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union 返回两个切片共同拥有的元素
|
||||||
|
func Union[S ~[]T, T Hashable](arr1 S, arr2 S) S {
|
||||||
|
hashArr, compArr := arr1, arr2
|
||||||
|
if len(arr1) < len(arr2) {
|
||||||
|
hashArr, compArr = compArr, hashArr
|
||||||
|
}
|
||||||
|
hash := map[T]struct{}{}
|
||||||
|
for _, item := range hashArr {
|
||||||
|
hash[item] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq := map[T]struct{}{}
|
||||||
|
ret := make(S, 0, len(compArr))
|
||||||
|
exists := false
|
||||||
|
for _, item := range compArr {
|
||||||
|
if _, exists = hash[item]; exists {
|
||||||
|
if _, exists = uniq[item]; !exists {
|
||||||
|
ret = append(ret, item)
|
||||||
|
uniq[item] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude 返回不包含的元素
|
||||||
|
func Exclude[S ~[]T, T Hashable](arr1 S, arr2 S) S {
|
||||||
|
diff := make([]T, 0, len(arr1))
|
||||||
|
hash := map[T]struct{}{}
|
||||||
|
for _, item := range arr2 {
|
||||||
|
hash[item] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range arr1 {
|
||||||
|
if _, exists := hash[item]; !exists {
|
||||||
|
diff = append(diff, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
// PadLeft 左边填充指定数量
|
||||||
|
func PadLeft[S ~[]T, T any](arr S, val T, count int) S {
|
||||||
|
prefix := make(S, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
prefix[i] = val
|
||||||
|
}
|
||||||
|
arr = append(prefix, arr...)
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// PadRight 右边填充指定数量
|
||||||
|
func PadRight[S ~[]T, T any](arr S, val T, count int) S {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
arr = append(arr, val)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveLeft 移除左侧相同元素
|
||||||
|
func RemoveLeft[S ~[]T, T comparable](arr S, val T) S {
|
||||||
|
for len(arr) > 0 && arr[0] == val {
|
||||||
|
arr = arr[1:]
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRight 移除右侧相同元素
|
||||||
|
func RemoveRight[S ~[]T, T comparable](arr S, val T) S {
|
||||||
|
for {
|
||||||
|
length := len(arr)
|
||||||
|
if length > 0 && arr[length-1] == val {
|
||||||
|
arr = arr[:length]
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count 统计制定条件元素数量
|
||||||
|
func Count[S ~[]T, T any](arr S, filter func(int) bool) int {
|
||||||
|
count := 0
|
||||||
|
for i := range arr {
|
||||||
|
if filter(i) {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group 根据分组函数对数组进行分组汇总
|
||||||
|
func Group[S ~[]T, T any, K Hashable, R any](arr S, groupFunc func(int) (K, R)) map[K][]R {
|
||||||
|
ret := map[K][]R{}
|
||||||
|
for i := range arr {
|
||||||
|
key, val := groupFunc(i)
|
||||||
|
ret[key] = append(ret[key], val)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -7,13 +7,12 @@ import useBrowserStore from 'stores/browser.js'
|
||||||
import Play from '@/components/icons/Play.vue'
|
import Play from '@/components/icons/Play.vue'
|
||||||
import Pause from '@/components/icons/Pause.vue'
|
import Pause from '@/components/icons/Pause.vue'
|
||||||
import { ExportLog, StartMonitor, StopMonitor } from 'wailsjs/go/services/monitorService.js'
|
import { ExportLog, StartMonitor, StopMonitor } from 'wailsjs/go/services/monitorService.js'
|
||||||
import { EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
|
import { ClipboardSetText, EventsOff, EventsOn } from 'wailsjs/runtime/runtime.js'
|
||||||
import Copy from '@/components/icons/Copy.vue'
|
import Copy from '@/components/icons/Copy.vue'
|
||||||
import Export from '@/components/icons/Export.vue'
|
import Export from '@/components/icons/Export.vue'
|
||||||
import Delete from '@/components/icons/Delete.vue'
|
import Delete from '@/components/icons/Delete.vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import Bottom from '@/components/icons/Bottom.vue'
|
import Bottom from '@/components/icons/Bottom.vue'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
|
||||||
|
@ -96,9 +95,16 @@ const onStopMonitor = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCopyLog = async () => {
|
const onCopyLog = async () => {
|
||||||
copy(join(data.list, '\n'))
|
try {
|
||||||
|
const content = join(data.list, '\n')
|
||||||
|
const succ = await ClipboardSetText(content)
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onExportLog = () => {
|
const onExportLog = () => {
|
||||||
ExportLog(data.list)
|
ExportLog(data.list)
|
||||||
|
|
|
@ -9,12 +9,12 @@ import RedisTypeTag from '@/components/common/RedisTypeTag.vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import Copy from '@/components/icons/Copy.vue'
|
import Copy from '@/components/icons/Copy.vue'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { computed, onMounted, onUnmounted, reactive, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, reactive, watch } from 'vue'
|
||||||
import { NIcon, useThemeVars } from 'naive-ui'
|
import { NIcon, useThemeVars } from 'naive-ui'
|
||||||
import { timeout } from '@/utils/promise.js'
|
import { timeout } from '@/utils/promise.js'
|
||||||
import AutoRefreshForm from '@/components/common/AutoRefreshForm.vue'
|
import AutoRefreshForm from '@/components/common/AutoRefreshForm.vue'
|
||||||
import { toHumanReadable } from '@/utils/date.js'
|
import { toHumanReadable } from '@/utils/date.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
@ -139,9 +139,16 @@ const onToggleRefresh = (on) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onCopyKey = () => {
|
const onCopyKey = () => {
|
||||||
copy(props.keyPath)
|
ClipboardSetText(props.keyPath)
|
||||||
|
.then((succ) => {
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
$message.error(e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const onTTL = () => {
|
const onTTL = () => {
|
||||||
dialogStore.openTTLDialog({
|
dialogStore.openTTLDialog({
|
||||||
|
|
|
@ -17,8 +17,8 @@ import Edit from '@/components/icons/Edit.vue'
|
||||||
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import { decodeRedisKey } from '@/utils/key_convert.js'
|
import { decodeRedisKey } from '@/utils/key_convert.js'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { formatBytes } from '@/utils/byte_convert.js'
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -230,8 +230,14 @@ const actionColumn = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCopy: async () => {
|
onCopy: async () => {
|
||||||
copy(row.v)
|
try {
|
||||||
|
const succ = await ClipboardSetText(row.v)
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onEdit: () => startEdit(index + 1, row.k, row.v),
|
onEdit: () => startEdit(index + 1, row.k, row.v),
|
||||||
onDelete: async () => {
|
onDelete: async () => {
|
||||||
|
|
|
@ -5,13 +5,13 @@ import Copy from '@/components/icons/Copy.vue'
|
||||||
import Save from '@/components/icons/Save.vue'
|
import Save from '@/components/icons/Save.vue'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { isEmpty, toLower } from 'lodash'
|
import { isEmpty, toLower } from 'lodash'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import { decodeRedisKey } from '@/utils/key_convert.js'
|
import { decodeRedisKey } from '@/utils/key_convert.js'
|
||||||
import ContentEditor from '@/components/content_value/ContentEditor.vue'
|
import ContentEditor from '@/components/content_value/ContentEditor.vue'
|
||||||
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
import { decodeTypes, formatTypes } from '@/consts/value_view_type.js'
|
||||||
import { formatBytes } from '@/utils/byte_convert.js'
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -62,9 +62,16 @@ const showMemoryUsage = computed(() => {
|
||||||
* Copy value
|
* Copy value
|
||||||
*/
|
*/
|
||||||
const onCopyValue = () => {
|
const onCopyValue = () => {
|
||||||
copy(displayValue.value)
|
ClipboardSetText(displayValue.value)
|
||||||
|
.then((succ) => {
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
$message.error(e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save value
|
* Save value
|
||||||
|
|
|
@ -16,8 +16,8 @@ import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vu
|
||||||
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import Edit from '@/components/icons/Edit.vue'
|
import Edit from '@/components/icons/Edit.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { formatBytes } from '@/utils/byte_convert.js'
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -180,8 +180,14 @@ const actionColumn = {
|
||||||
editing: false,
|
editing: false,
|
||||||
bindKey: `#${index + 1}`,
|
bindKey: `#${index + 1}`,
|
||||||
onCopy: async () => {
|
onCopy: async () => {
|
||||||
copy(row.v)
|
try {
|
||||||
|
const succ = await ClipboardSetText(row.v)
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onEdit: () => {
|
onEdit: () => {
|
||||||
startEdit(index + 1, row.v)
|
startEdit(index + 1, row.v)
|
||||||
|
|
|
@ -16,8 +16,8 @@ import Edit from '@/components/icons/Edit.vue'
|
||||||
import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue'
|
import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vue'
|
||||||
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { formatBytes } from '@/utils/byte_convert.js'
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -177,8 +177,14 @@ const actionColumn = {
|
||||||
editing: false,
|
editing: false,
|
||||||
bindKey: `#${index + 1}`,
|
bindKey: `#${index + 1}`,
|
||||||
onCopy: async () => {
|
onCopy: async () => {
|
||||||
copy(row.v)
|
try {
|
||||||
|
const succ = await ClipboardSetText(row.v)
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onEdit: () => {
|
onEdit: () => {
|
||||||
startEdit(index + 1, row.v)
|
startEdit(index + 1, row.v)
|
||||||
|
|
|
@ -13,8 +13,8 @@ import LoadList from '@/components/icons/LoadList.vue'
|
||||||
import LoadAll from '@/components/icons/LoadAll.vue'
|
import LoadAll from '@/components/icons/LoadAll.vue'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { formatBytes } from '@/utils/byte_convert.js'
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -108,8 +108,14 @@ const actionColumn = {
|
||||||
bindKey: row.id,
|
bindKey: row.id,
|
||||||
readonly: true,
|
readonly: true,
|
||||||
onCopy: async () => {
|
onCopy: async () => {
|
||||||
copy(JSON.stringify(row.v))
|
try {
|
||||||
|
const succ = await ClipboardSetText(JSON.stringify(row.v))
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onDelete: async () => {
|
onDelete: async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -6,13 +6,13 @@ import Save from '@/components/icons/Save.vue'
|
||||||
import { useThemeVars } from 'naive-ui'
|
import { useThemeVars } from 'naive-ui'
|
||||||
import { formatTypes } from '@/consts/value_view_type.js'
|
import { formatTypes } from '@/consts/value_view_type.js'
|
||||||
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
import { types as redisTypes } from '@/consts/support_redis_type.js'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { isEmpty, toLower } from 'lodash'
|
import { isEmpty, toLower } from 'lodash'
|
||||||
import useBrowserStore from 'stores/browser.js'
|
import useBrowserStore from 'stores/browser.js'
|
||||||
import { decodeRedisKey } from '@/utils/key_convert.js'
|
import { decodeRedisKey } from '@/utils/key_convert.js'
|
||||||
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import ContentEditor from '@/components/content_value/ContentEditor.vue'
|
import ContentEditor from '@/components/content_value/ContentEditor.vue'
|
||||||
import { formatBytes } from '@/utils/byte_convert.js'
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -121,9 +121,16 @@ const onFormatChanged = async (decode = '', format = '') => {
|
||||||
* Copy value
|
* Copy value
|
||||||
*/
|
*/
|
||||||
const onCopyValue = () => {
|
const onCopyValue = () => {
|
||||||
copy(displayValue.value)
|
ClipboardSetText(displayValue.value)
|
||||||
|
.then((succ) => {
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
$message.error(e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save value
|
* Save value
|
||||||
|
|
|
@ -16,8 +16,8 @@ import ContentEntryEditor from '@/components/content_value/ContentEntryEditor.vu
|
||||||
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
import FormatSelector from '@/components/content_value/FormatSelector.vue'
|
||||||
import Edit from '@/components/icons/Edit.vue'
|
import Edit from '@/components/icons/Edit.vue'
|
||||||
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
import ContentSearchInput from '@/components/content_value/ContentSearchInput.vue'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import { formatBytes } from '@/utils/byte_convert.js'
|
import { formatBytes } from '@/utils/byte_convert.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
const themeVars = useThemeVars()
|
const themeVars = useThemeVars()
|
||||||
|
@ -224,8 +224,14 @@ const actionColumn = {
|
||||||
editing: false,
|
editing: false,
|
||||||
bindKey: row.v,
|
bindKey: row.v,
|
||||||
onCopy: async () => {
|
onCopy: async () => {
|
||||||
copy(row.v)
|
try {
|
||||||
|
const succ = await ClipboardSetText(row.v)
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
$message.error(e.message)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onEdit: () => startEdit(index + 1, row.s, row.v),
|
onEdit: () => startEdit(index + 1, row.s, row.v),
|
||||||
onDelete: async () => {
|
onDelete: async () => {
|
||||||
|
|
|
@ -507,7 +507,7 @@ const pasteFromClipboard = async () => {
|
||||||
</n-radio-group>
|
</n-radio-group>
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi
|
<n-form-item-gi
|
||||||
v-if="generalForm.dbFilterType !== 'none'"
|
v-show="generalForm.dbFilterType !== 'none'"
|
||||||
:label="$t('dialogue.connection.advn.dbfilter_input')"
|
:label="$t('dialogue.connection.advn.dbfilter_input')"
|
||||||
:span="24">
|
:span="24">
|
||||||
<n-select
|
<n-select
|
||||||
|
|
|
@ -130,7 +130,7 @@ const onClose = () => {
|
||||||
required>
|
required>
|
||||||
<n-input v-model:value="deleteForm.key" placeholder="" @input="resetAffected" />
|
<n-input v-model:value="deleteForm.key" placeholder="" @input="resetAffected" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-checkbox v-if="!deleteForm.showAffected" v-model:checked="deleteForm.direct">
|
<n-checkbox v-model:checked="deleteForm.direct">
|
||||||
{{ $t('dialogue.key.direct_delete') }}
|
{{ $t('dialogue.key.direct_delete') }}
|
||||||
</n-checkbox>
|
</n-checkbox>
|
||||||
<n-card
|
<n-card
|
||||||
|
|
|
@ -13,6 +13,7 @@ import Add from '@/components/icons/Add.vue'
|
||||||
import Layer from '@/components/icons/Layer.vue'
|
import Layer from '@/components/icons/Layer.vue'
|
||||||
import Delete from '@/components/icons/Delete.vue'
|
import Delete from '@/components/icons/Delete.vue'
|
||||||
import useDialogStore from 'stores/dialog.js'
|
import useDialogStore from 'stores/dialog.js'
|
||||||
|
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
|
||||||
import useConnectionStore from 'stores/connections.js'
|
import useConnectionStore from 'stores/connections.js'
|
||||||
import useTabStore from 'stores/tab.js'
|
import useTabStore from 'stores/tab.js'
|
||||||
import IconButton from '@/components/common/IconButton.vue'
|
import IconButton from '@/components/common/IconButton.vue'
|
||||||
|
@ -26,7 +27,6 @@ import usePreferencesStore from 'stores/preferences.js'
|
||||||
import { typesIconStyle } from '@/consts/support_redis_type.js'
|
import { typesIconStyle } from '@/consts/support_redis_type.js'
|
||||||
import { nativeRedisKey } from '@/utils/key_convert.js'
|
import { nativeRedisKey } from '@/utils/key_convert.js'
|
||||||
import { isMacOS } from '@/utils/platform.js'
|
import { isMacOS } from '@/utils/platform.js'
|
||||||
import copy from 'copy-text-to-clipboard'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
server: String,
|
server: String,
|
||||||
|
@ -321,9 +321,16 @@ const handleKeyCopy = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === ConnectionType.RedisValue) {
|
if (node.type === ConnectionType.RedisValue) {
|
||||||
copy(nativeRedisKey(node.redisKeyCode || node.redisKey))
|
ClipboardSetText(nativeRedisKey(node.redisKeyCode || node.redisKey))
|
||||||
|
.then((succ) => {
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
$message.error(e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onKeyShortcut = (e) => {
|
const onKeyShortcut = (e) => {
|
||||||
|
@ -406,8 +413,15 @@ const handleSelectContextMenu = (action) => {
|
||||||
break
|
break
|
||||||
case 'key_copy':
|
case 'key_copy':
|
||||||
case 'value_copy':
|
case 'value_copy':
|
||||||
copy(nativeRedisKey(redisKey))
|
ClipboardSetText(nativeRedisKey(redisKey))
|
||||||
|
.then((succ) => {
|
||||||
|
if (succ) {
|
||||||
$message.success(i18n.t('interface.copy_succ'))
|
$message.success(i18n.t('interface.copy_succ'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
$message.error(e.message)
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case 'db_loadall':
|
case 'db_loadall':
|
||||||
if (node != null && !!!node.loading) {
|
if (node != null && !!!node.loading) {
|
||||||
|
|
|
@ -437,7 +437,7 @@ const findSiblingsAndIndex = (node, nodes) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// delay save until drop stopped after 2 seconds
|
// delay save until drop stopped after 2 seconds
|
||||||
const saveSort = debounce(connectionStore.saveConnectionSorted, 1500, { trailing: true })
|
const saveSort = debounce(connectionStore.saveConnectionSorted, 2000, { trailing: true })
|
||||||
const handleDrop = ({ node, dragNode, dropPosition }) => {
|
const handleDrop = ({ node, dragNode, dropPosition }) => {
|
||||||
const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(dragNode, connectionStore.connections)
|
const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(dragNode, connectionStore.connections)
|
||||||
if (dragNodeSiblings === null || dragNodeIndex === null) {
|
if (dragNodeSiblings === null || dragNodeIndex === null) {
|
||||||
|
|
Loading…
Reference in New Issue