feat(redis): add key statistics tracking for database browser (#269)

## 背景说明
为 Redis 数据库浏览器增加键统计功能,显示已加载 key 数/总 key 数,
并在动态加载、删除、清空时实时更新。

## 修改内容
- 新增 \RedisDatabaseInfo\ 结构体(db + keys),替代原来的 \Vec<u32>\
- \list_databases\ 返回每个 db 的 key 数量(解析 INFO keyspace)
- \scan_keys_page\ 增加 DBSIZE 调用获取 total_keys
- 前端 TreeNode 增加 loadedKeyCount/totalKeyCount 字段
- 树节点标签动态显示 \db0 (loaded/total)\ 格式
- 新增 \updateRedisDbKeyStats\ 在加载/删除/清空时更新统计

## 测试
- 建议补充 list_keyspace_databases 解析异常输入的单测
- 建议补充 redisDbLabel 边界值测试
This commit is contained in:
Abeautifulsnow authored and GitHub committed 2026-05-14 16:24:46 +08:00
1 parent 7faa9ec818
commit 919c3d58c1
10 files changed
+94 -20

No files matched your search

+16 -1
View File
@@ -25,6 +25,7 @@ import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
import RedisValueViewer from "./RedisValueViewer.vue";
import * as api from "@/lib/api";
import type { RedisKeyInfo } from "@/lib/api";
import { useConnectionStore } from "@/stores/connectionStore";
import {
buildRedisKeyTree,
collectExpandedGroupIds,
@@ -36,6 +37,7 @@ import { classifyRedisCommandSafety } from "@/lib/redisCommandSafety";
import { isCancelSearchShortcut } from "@/lib/keyboardShortcuts";
const { t } = useI18n();
const connectionStore = useConnectionStore();
const props = defineProps<{
connectionId: string;
@@ -134,6 +136,10 @@ async function scanNextPage() {
scanCursor.value = result.cursor;
hasMore.value = result.cursor !== 0;
rebuildTree(isSearchMode.value);
connectionStore.updateRedisDbKeyStats(props.connectionId, props.db, {
loaded: isSearchMode.value ? undefined : flatKeys.value.length,
total: result.total_keys,
});
}
async function loadKeys() {
@@ -180,6 +186,10 @@ function onKeyDeleted() {
flatKeys.value = flatKeys.value.filter((key) => key.key_raw !== selectedKeyRaw.value);
selectedKeyRaw.value = null;
rebuildTree(false);
connectionStore.updateRedisDbKeyStats(props.connectionId, props.db, {
loaded: isSearchMode.value ? undefined : flatKeys.value.length,
totalDelta: -1,
});
}
function toggleCheck(keyRaw: string, event: Event) {
@@ -220,7 +230,7 @@ function resetLoadedKeys() {
}
async function deleteKeyRaws(keys: string[]) {
await api.redisDeleteKeys(props.connectionId, props.db, keys);
const deletedCount = await api.redisDeleteKeys(props.connectionId, props.db, keys);
const deleted = new Set(keys);
flatKeys.value = flatKeys.value.filter((k) => !deleted.has(k.key_raw));
if (selectedKeyRaw.value && deleted.has(selectedKeyRaw.value)) {
@@ -228,6 +238,10 @@ async function deleteKeyRaws(keys: string[]) {
}
checkedKeys.value = new Set();
rebuildTree(false);
connectionStore.updateRedisDbKeyStats(props.connectionId, props.db, {
loaded: isSearchMode.value ? undefined : flatKeys.value.length,
totalDelta: -deletedCount,
});
}
async function runRedisCommand(command: string) {
@@ -280,6 +294,7 @@ async function applyDangerAction() {
} else if (pending.kind === "flush-db") {
await api.redisFlushDb(props.connectionId, props.db);
resetLoadedKeys();
connectionStore.updateRedisDbKeyStats(props.connectionId, props.db, { loaded: 0, total: 0 });
} else {
await runRedisCommand(pending.command);
}