mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-07 02:51:35 +08:00
🔧 fix(redis/ui): 统一 Redis 工作台交互样式并修复 Tree 节点异常高亮
- Redis 页面重构为工作台样式,统一左右面板、工具条和详情区层级 - 接入 light/dark/透明模式主题参数,修复 Redis 页面与全局主题不一致问题 - 新增文件夹递归勾选、全选全部、分组全选/取消全选能力 - 支持 Redis Key 右键菜单重命名并同步更新树节点、选中态和详情面板 - 修复 type=none 时读取失败问题,过期或已删除 Key 自动提示并移出列表 - 接管 Redis Tree 展开箭头渲染,修复 switcher 命中区错位和悬浮白线问题 - 统一工具、代理、主题、关于、筛选、新建组和新建连接等弹层主题 - refs #231
This commit is contained in:
@@ -453,6 +453,23 @@ func (a *App) RedisRenameKey(config connection.ConnectionConfig, oldKey, newKey
|
||||
return connection.QueryResult{Success: true, Message: "重命名成功"}
|
||||
}
|
||||
|
||||
// RedisKeyExists checks whether a key already exists
|
||||
func (a *App) RedisKeyExists(config connection.ConnectionConfig, key string) connection.QueryResult {
|
||||
config.Type = "redis"
|
||||
client, err := a.getRedisClient(config)
|
||||
if err != nil {
|
||||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||||
}
|
||||
|
||||
exists, err := client.KeyExists(key)
|
||||
if err != nil {
|
||||
logger.Error(err, "RedisKeyExists 检查失败:key=%s", key)
|
||||
return connection.QueryResult{Success: false, Message: err.Error()}
|
||||
}
|
||||
|
||||
return connection.QueryResult{Success: true, Data: map[string]bool{"exists": exists}}
|
||||
}
|
||||
|
||||
// RedisDeleteHashField deletes fields from a hash
|
||||
func (a *App) RedisDeleteHashField(config connection.ConnectionConfig, key string, fields []string) connection.QueryResult {
|
||||
config.Type = "redis"
|
||||
|
||||
@@ -3,6 +3,7 @@ package redis
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -18,6 +19,8 @@ import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ErrRedisKeyGone = errors.New("Redis Key 不存在或已过期")
|
||||
|
||||
// RedisClientImpl implements RedisClient using go-redis
|
||||
type RedisClientImpl struct {
|
||||
client redis.UniversalClient
|
||||
@@ -472,20 +475,29 @@ func (r *RedisClientImpl) loadRedisKeyInfos(ctx context.Context, keys []string)
|
||||
if ttlErr != nil && ttlErr != redis.Nil {
|
||||
ttlValue = -2
|
||||
}
|
||||
ttlSeconds := toRedisTTLSeconds(ttlValue)
|
||||
if isRedisKeyGone(keyType, ttlSeconds) {
|
||||
continue
|
||||
}
|
||||
result = append(result, RedisKeyInfo{
|
||||
Key: r.toDisplayKey(key),
|
||||
Type: keyType,
|
||||
TTL: toRedisTTLSeconds(ttlValue),
|
||||
TTL: ttlSeconds,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
for i, key := range keys {
|
||||
keyType := typeResults[i].Val()
|
||||
ttlSeconds := toRedisTTLSeconds(ttlResults[i].Val())
|
||||
if isRedisKeyGone(keyType, ttlSeconds) {
|
||||
continue
|
||||
}
|
||||
result = append(result, RedisKeyInfo{
|
||||
Key: r.toDisplayKey(key),
|
||||
Type: typeResults[i].Val(),
|
||||
TTL: toRedisTTLSeconds(ttlResults[i].Val()),
|
||||
Type: keyType,
|
||||
TTL: ttlSeconds,
|
||||
})
|
||||
}
|
||||
return result
|
||||
@@ -501,6 +513,17 @@ func toRedisTTLSeconds(ttl time.Duration) int64 {
|
||||
return int64(ttl.Seconds())
|
||||
}
|
||||
|
||||
func isRedisKeyGone(keyType string, ttl int64) bool {
|
||||
return keyType == "none" || ttl == -2
|
||||
}
|
||||
|
||||
func normalizeRedisGetValueError(keyType string, ttl int64) error {
|
||||
if isRedisKeyGone(keyType, ttl) {
|
||||
return ErrRedisKeyGone
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKeyType returns the type of a key
|
||||
func (r *RedisClientImpl) GetKeyType(key string) (string, error) {
|
||||
if r.client == nil {
|
||||
@@ -594,6 +617,9 @@ func (r *RedisClientImpl) GetValue(key string) (*RedisValue, error) {
|
||||
}
|
||||
|
||||
ttl, _ := r.GetTTL(key)
|
||||
if err := normalizeRedisGetValueError(keyType, ttl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
physicalKey := r.toPhysicalKey(key)
|
||||
|
||||
result := &RedisValue{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package redis
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSanitizeRedisPassword(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -79,3 +82,40 @@ func TestSanitizeRedisPassword(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRedisKeyGone(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
keyType string
|
||||
ttl int64
|
||||
want bool
|
||||
}{
|
||||
{name: "type none", keyType: "none", ttl: -2, want: true},
|
||||
{name: "type none without ttl", keyType: "none", ttl: -1, want: true},
|
||||
{name: "missing by ttl", keyType: "string", ttl: -2, want: true},
|
||||
{name: "normal string", keyType: "string", ttl: 30, want: false},
|
||||
{name: "permanent hash", keyType: "hash", ttl: -1, want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isRedisKeyGone(tt.keyType, tt.ttl); got != tt.want {
|
||||
t.Fatalf("isRedisKeyGone(%q, %d)=%v, want %v", tt.keyType, tt.ttl, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRedisGetValueError(t *testing.T) {
|
||||
err := normalizeRedisGetValueError("none", -2)
|
||||
if !errors.Is(err, ErrRedisKeyGone) {
|
||||
t.Fatalf("expected ErrRedisKeyGone, got %v", err)
|
||||
}
|
||||
if err == nil || err.Error() != "Redis Key 不存在或已过期" {
|
||||
t.Fatalf("unexpected error text: %v", err)
|
||||
}
|
||||
|
||||
if normalizeRedisGetValueError("hash", -1) != nil {
|
||||
t.Fatal("expected nil for supported existing key")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user