mirror of
https://github.com/t8y2/dbx.git
synced 2026-11-01 00:25:38 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function useDatabaseOptions() {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
if (connection.db_type === "redis") {
|
||||
const dbs = await api.redisListDatabases(connectionId);
|
||||
databaseOptions.value[connectionId] = dbs.map(String);
|
||||
databaseOptions.value[connectionId] = dbs.map((db) => String(db.db));
|
||||
} else if (connection.db_type === "mongodb") {
|
||||
databaseOptions.value[connectionId] = await api.mongoListDatabases(connectionId);
|
||||
} else {
|
||||
|
||||
@@ -156,6 +156,7 @@ export type {
|
||||
AiChatMessage,
|
||||
AiConversation,
|
||||
UpdateInfo,
|
||||
RedisDatabaseInfo,
|
||||
RedisKeyInfo,
|
||||
RedisValue,
|
||||
RedisScanResult,
|
||||
|
||||
+2
-1
@@ -24,6 +24,7 @@ import type {
|
||||
AiStreamChunk,
|
||||
AiConversation,
|
||||
UpdateInfo,
|
||||
RedisDatabaseInfo,
|
||||
RedisValue,
|
||||
RedisScanResult,
|
||||
RedisCommandResult,
|
||||
@@ -547,7 +548,7 @@ export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
||||
// Redis
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function redisListDatabases(connectionId: string): Promise<number[]> {
|
||||
export async function redisListDatabases(connectionId: string): Promise<RedisDatabaseInfo[]> {
|
||||
return post("/api/redis/list-databases", { connectionId });
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -355,6 +355,11 @@ export interface RedisKeyInfo {
|
||||
value_preview: string;
|
||||
}
|
||||
|
||||
export interface RedisDatabaseInfo {
|
||||
db: number;
|
||||
keys: number;
|
||||
}
|
||||
|
||||
export interface RedisValue {
|
||||
key_display: string;
|
||||
key_raw: string;
|
||||
@@ -369,6 +374,7 @@ export interface RedisValue {
|
||||
export interface RedisScanResult {
|
||||
cursor: number;
|
||||
keys: RedisKeyInfo[];
|
||||
total_keys: number;
|
||||
}
|
||||
|
||||
export type RedisCommandSafety = "allowed" | "confirm" | "blocked";
|
||||
@@ -379,7 +385,7 @@ export interface RedisCommandResult {
|
||||
value: any;
|
||||
}
|
||||
|
||||
export async function redisListDatabases(connectionId: string): Promise<number[]> {
|
||||
export async function redisListDatabases(connectionId: string): Promise<RedisDatabaseInfo[]> {
|
||||
return invoke("redis_list_databases", { connectionId });
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ interface LoadTreeOptions {
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
function redisDbLabel(db: number, loadedKeyCount?: number, totalKeyCount?: number): string {
|
||||
if (totalKeyCount == null) return `db${db}`;
|
||||
return `db${db} (${loadedKeyCount ?? 0}/${totalKeyCount})`;
|
||||
}
|
||||
|
||||
export const useConnectionStore = defineStore("connection", () => {
|
||||
const connections = ref<ConnectionConfig[]>([]);
|
||||
const isDesktop = isTauriRuntime();
|
||||
@@ -581,11 +586,13 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
withSavedSqlRoot(
|
||||
connectionId,
|
||||
dbs.map((db) => ({
|
||||
id: `${connectionId}:db${db}`,
|
||||
label: `db${db}`,
|
||||
id: `${connectionId}:db${db.db}`,
|
||||
label: redisDbLabel(db.db, 0, db.keys),
|
||||
type: "redis-db" as const,
|
||||
connectionId,
|
||||
database: String(db),
|
||||
database: String(db.db),
|
||||
loadedKeyCount: 0,
|
||||
totalKeyCount: db.keys,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
})),
|
||||
@@ -598,6 +605,21 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
|
||||
function updateRedisDbKeyStats(
|
||||
connectionId: string,
|
||||
db: number,
|
||||
stats: { loaded?: number; total?: number; totalDelta?: number },
|
||||
) {
|
||||
const node = findNode(treeNodes.value, `${connectionId}:db${db}`);
|
||||
if (!node || node.type !== "redis-db") return;
|
||||
if (stats.loaded != null) node.loadedKeyCount = stats.loaded;
|
||||
if (stats.total != null) node.totalKeyCount = stats.total;
|
||||
if (stats.totalDelta != null && node.totalKeyCount != null) {
|
||||
node.totalKeyCount = Math.max(0, node.totalKeyCount + stats.totalDelta);
|
||||
}
|
||||
node.label = redisDbLabel(db, node.loadedKeyCount, node.totalKeyCount);
|
||||
}
|
||||
|
||||
async function loadMongoDatabases(connectionId: string) {
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node) return;
|
||||
@@ -1442,6 +1464,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
initFromDisk,
|
||||
loadDatabases,
|
||||
loadRedisDatabases,
|
||||
updateRedisDbKeyStats,
|
||||
loadMongoDatabases,
|
||||
loadMongoCollections,
|
||||
loadSchemas,
|
||||
|
||||
@@ -231,6 +231,8 @@ export interface TreeNode {
|
||||
schema?: string;
|
||||
tableName?: string;
|
||||
objectCount?: number;
|
||||
loadedKeyCount?: number;
|
||||
totalKeyCount?: number;
|
||||
hiddenChildren?: TreeNode[];
|
||||
savedSqlId?: string;
|
||||
savedSqlFolderId?: string;
|
||||
|
||||
Reference in New Issue
Block a user