fix(redis): handle empty scan batches and exact key lookup fallback (#1992)
This commit is contained in:
parent
b85b14bfde
commit
388d59fb4a
|
|
@ -126,6 +126,13 @@ const fetchAllProgressText = computed(() => {
|
|||
}
|
||||
return t("redis.fetchAllProgressUnknown", { loaded: flatKeys.value.length });
|
||||
});
|
||||
const keyCountText = computed(() => {
|
||||
if (loading.value && flatKeys.value.length === 0) return loadingEmptyText.value;
|
||||
if (!isSearchMode.value && lastTotalKeys.value > 0) {
|
||||
return t("redis.loadedKeys", { loaded: flatKeys.value.length, total: lastTotalKeys.value });
|
||||
}
|
||||
return t("redis.keys", { count: flatKeys.value.length });
|
||||
});
|
||||
const selectedKey = computed(() => flatKeys.value.find((key) => key.key_raw === selectedKeyRaw.value) ?? null);
|
||||
const dangerDetails = computed(() => {
|
||||
if (!pendingDanger.value) return "";
|
||||
|
|
@ -268,23 +275,6 @@ async function streamValueSearch(requestId: number) {
|
|||
}
|
||||
}
|
||||
|
||||
async function fillInitialKeyBatch(requestId: number) {
|
||||
// Initial batch should fetch a substantial number of keys regardless of
|
||||
// redisScanPageSize. We scan in batches of at most 8 iterations each and
|
||||
// keep going until we've done at least 16 total iterations (enough to cover
|
||||
// most key spaces even with COUNT=200) or hasMore becomes false.
|
||||
const MAX_TOTAL_ITERATIONS = 16;
|
||||
const BATCH_ITERATIONS = 8;
|
||||
let totalIters = 0;
|
||||
while (requestId === searchRequestId && hasMore.value && totalIters < MAX_TOTAL_ITERATIONS) {
|
||||
const batchSize = Math.min(BATCH_ITERATIONS, MAX_TOTAL_ITERATIONS - totalIters);
|
||||
const result = await fetchScanBatchPage(batchSize);
|
||||
if (requestId !== searchRequestId) return;
|
||||
appendScanResult(result);
|
||||
totalIters += batchSize;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadKeys() {
|
||||
if (!redisBrowserIsActive) return;
|
||||
const requestId = ++searchRequestId;
|
||||
|
|
@ -297,18 +287,15 @@ async function loadKeys() {
|
|||
checkedKeys.value = new Set();
|
||||
expandedGroupIds.value = new Set();
|
||||
scanCursor.value = 0;
|
||||
lastTotalKeys.value = 0;
|
||||
try {
|
||||
if (isValueSearchMode.value && !valueQuery.value) {
|
||||
hasMore.value = false;
|
||||
return;
|
||||
}
|
||||
const applied = await scanNextPage(requestId);
|
||||
if (applied) {
|
||||
if (isValueSearchMode.value) {
|
||||
await streamValueSearch(requestId);
|
||||
} else {
|
||||
await fillInitialKeyBatch(requestId);
|
||||
}
|
||||
if (applied && isValueSearchMode.value) {
|
||||
await streamValueSearch(requestId);
|
||||
}
|
||||
} finally {
|
||||
if (requestId === searchRequestId) {
|
||||
|
|
@ -440,6 +427,7 @@ function resetLoadedKeys() {
|
|||
checkedKeys.value = new Set();
|
||||
expandedGroupIds.value = new Set();
|
||||
hasMore.value = false;
|
||||
lastTotalKeys.value = 0;
|
||||
}
|
||||
|
||||
async function deleteKeyRaws(keys: string[]) {
|
||||
|
|
@ -986,12 +974,21 @@ defineExpose({ focusSearch });
|
|||
<Button variant="ghost" size="icon" class="h-6 w-6 shrink-0" :title="t('redis.createKey')" @click="openCreateKeyDialog">
|
||||
<Plus class="h-3 w-3" />
|
||||
</Button>
|
||||
<span class="text-xs text-muted-foreground shrink-0 ml-1">{{ loading && flatKeys.length === 0 ? loadingEmptyText : t("redis.keys", { count: flatKeys.length }) }}</span>
|
||||
<span class="text-xs text-muted-foreground shrink-0 ml-1">{{ keyCountText }}</span>
|
||||
<Button v-if="checkedKeys.size > 0" variant="ghost" size="sm" class="h-6 text-xs text-destructive shrink-0 ml-1" @click="requestBatchDelete"> <Trash2 class="w-3 h-3 mr-1" />{{ checkedKeys.size }} </Button>
|
||||
</div>
|
||||
|
||||
<div v-if="flatKeys.length === 0 && !loading" class="flex-1 flex items-center justify-center text-muted-foreground text-xs">
|
||||
{{ t("redis.noKeys") }}
|
||||
<div v-if="flatKeys.length === 0 && !loading" class="flex-1 flex flex-col items-center justify-center text-muted-foreground text-xs p-4 text-center">
|
||||
<template v-if="hasMore">
|
||||
<span class="mb-3">{{ t("redis.noKeysInScanHint") }}</span>
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs" :disabled="loadingMore" @click="loadMore">
|
||||
<Loader2 v-if="loadingMore" class="w-3 h-3 mr-1.5 animate-spin" />
|
||||
{{ t("redis.loadMoreKeys") }}
|
||||
</Button>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t("redis.noKeys") }}
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="loading && flatKeys.length === 0" class="flex-1 flex items-center justify-center gap-2 text-muted-foreground text-xs">
|
||||
<Loader2 class="w-3.5 h-3.5 animate-spin" />
|
||||
|
|
|
|||
|
|
@ -1659,6 +1659,7 @@ export default {
|
|||
redis: {
|
||||
selectKey: "Select a key to view its value",
|
||||
noKeys: "No keys found",
|
||||
noKeysInScanHint: "No keys found in current scan range",
|
||||
pattern: "pattern (e.g. user:*)",
|
||||
fuzzyPattern: "key contains...",
|
||||
fuzzyMatch: "Fuzzy",
|
||||
|
|
@ -1669,6 +1670,7 @@ export default {
|
|||
searchByValue: "Value",
|
||||
searchByAll: "All",
|
||||
keys: "{count} keys",
|
||||
loadedKeys: "{loaded} / {total} keys loaded",
|
||||
loadingKeys: "Loading keys...",
|
||||
searchingValues: "Searching values...",
|
||||
searchingAll: "Searching keys and values...",
|
||||
|
|
|
|||
|
|
@ -1321,6 +1321,7 @@ export default {
|
|||
redis: {
|
||||
selectKey: "Selecciona una clave para ver su valor",
|
||||
noKeys: "No se encontraron claves",
|
||||
noKeysInScanHint: "No se encontraron claves en el rango de escaneo actual",
|
||||
pattern: "patrón (p. ej. usuario:*)",
|
||||
fuzzyPattern: "la clave contiene...",
|
||||
fuzzyMatch: "Difusa",
|
||||
|
|
@ -1331,6 +1332,7 @@ export default {
|
|||
searchByValue: "Valor",
|
||||
searchByAll: "Todo",
|
||||
keys: "{count} claves",
|
||||
loadedKeys: "{loaded} / {total} claves cargadas",
|
||||
loadingKeys: "Cargando claves...",
|
||||
searchingValues: "Buscando por valor...",
|
||||
searchingAll: "Buscando claves y valores...",
|
||||
|
|
|
|||
|
|
@ -1445,6 +1445,7 @@ export default {
|
|||
redis: {
|
||||
selectKey: "Seleziona una chiave per visualizzarne il valore",
|
||||
noKeys: "Nessuna chiave trovata",
|
||||
noKeysInScanHint: "Nessuna chiave trovata nell'intervallo di scansione corrente",
|
||||
pattern: "pattern (es. user:*)",
|
||||
fuzzyPattern: "la chiave contiene...",
|
||||
fuzzyMatch: "Fuzzy",
|
||||
|
|
@ -1455,6 +1456,7 @@ export default {
|
|||
searchByValue: "Valore",
|
||||
searchByAll: "Tutto",
|
||||
keys: "{count} chiavi",
|
||||
loadedKeys: "{loaded} / {total} chiavi caricate",
|
||||
loadingKeys: "Caricamento chiavi...",
|
||||
searchingValues: "Ricerca nei valori...",
|
||||
searchingAll: "Ricerca in chiavi e valori...",
|
||||
|
|
|
|||
|
|
@ -1603,9 +1603,11 @@ export default {
|
|||
searchByValue: "値",
|
||||
searchByAll: "すべて",
|
||||
keys: "{count}キー",
|
||||
loadedKeys: "{loaded}/{total}キー読み込み完了",
|
||||
loadingKeys: "キーを読み込み中...",
|
||||
searchingValues: "値を検索中...",
|
||||
searchingAll: "キーと値を検索中...",
|
||||
noKeysInScanHint: "現在のスキャン範囲で一致するキーは見つかりません",
|
||||
loadMoreKeys: "さらにキーを読み込む",
|
||||
fetchAllKeys: "すべて取得",
|
||||
stopFetchAll: "停止",
|
||||
|
|
|
|||
|
|
@ -1456,6 +1456,7 @@ export default {
|
|||
redis: {
|
||||
selectKey: "Selecione uma chave para ver seu valor",
|
||||
noKeys: "Nenhuma chave encontrada",
|
||||
noKeysInScanHint: "Nenhuma chave encontrada no intervalo atual de verificação",
|
||||
pattern: "padrão (ex.: user:*)",
|
||||
fuzzyPattern: "a chave contém...",
|
||||
fuzzyMatch: "Aproximado",
|
||||
|
|
@ -1466,6 +1467,7 @@ export default {
|
|||
searchByValue: "Valor",
|
||||
searchByAll: "Tudo",
|
||||
keys: "{count} chaves",
|
||||
loadedKeys: "{loaded} / {total} chaves carregadas",
|
||||
loadingKeys: "Carregando chaves...",
|
||||
searchingValues: "Pesquisando valores...",
|
||||
searchingAll: "Pesquisando chaves e valores...",
|
||||
|
|
|
|||
|
|
@ -1658,6 +1658,7 @@ export default {
|
|||
redis: {
|
||||
selectKey: "选择一个 key 查看值",
|
||||
noKeys: "未找到 key",
|
||||
noKeysInScanHint: "当前扫描范围内未命中匹配的 key",
|
||||
pattern: "匹配模式 (如 user:*)",
|
||||
fuzzyPattern: "输入关键字搜索 key",
|
||||
fuzzyMatch: "模糊",
|
||||
|
|
@ -1668,6 +1669,7 @@ export default {
|
|||
searchByValue: "值",
|
||||
searchByAll: "全部",
|
||||
keys: "{count} 个 key",
|
||||
loadedKeys: "已加载 {loaded} / 共 {total} 个 key",
|
||||
loadingKeys: "正在加载 key...",
|
||||
searchingValues: "正在按值搜索...",
|
||||
searchingAll: "正在搜索 key 和值...",
|
||||
|
|
|
|||
|
|
@ -1436,6 +1436,7 @@ export default {
|
|||
redis: {
|
||||
selectKey: "選擇一個 key 檢視值",
|
||||
noKeys: "未找到 key",
|
||||
noKeysInScanHint: "目前掃描範圍內未找到匹配的 key",
|
||||
pattern: "模式 (例如 user:*)",
|
||||
fuzzyPattern: "輸入關鍵字搜尋 key",
|
||||
fuzzyMatch: "模糊",
|
||||
|
|
@ -1446,6 +1447,7 @@ export default {
|
|||
searchByValue: "值",
|
||||
searchByAll: "全部",
|
||||
keys: "{count} 個 key",
|
||||
loadedKeys: "已載入 {loaded} / 共 {total} 個 key",
|
||||
loadingKeys: "正在載入 key……",
|
||||
searchingValues: "正在按值搜尋……",
|
||||
searchingAll: "正在搜尋鍵和值……",
|
||||
|
|
|
|||
|
|
@ -1495,7 +1495,37 @@ where
|
|||
let iterations = max_iterations.max(1);
|
||||
let total_keys: u64 = if cursor == 0 { redis::cmd("DBSIZE").query_async(con).await.unwrap_or(0) } else { 0 };
|
||||
|
||||
let is_exact_match = !pattern.contains('*') && !pattern.contains('?') && !pattern.contains('[');
|
||||
if cursor == 0 && is_exact_match && !pattern.is_empty() {
|
||||
match redis::cmd("EXISTS").arg(pattern).query_async::<bool>(con).await {
|
||||
Ok(true) => {
|
||||
let key_type: String = if include_types {
|
||||
redis::cmd("TYPE").arg(pattern).query_async(con).await.unwrap_or_else(|_| "unknown".to_string())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let value_preview = if include_types { redis_key_value_preview(&key_type) } else { String::new() };
|
||||
|
||||
let key_info = RedisKeyInfo {
|
||||
key_display: redis_key_bytes_to_display(pattern.as_bytes()),
|
||||
key_raw: redis_key_bytes_to_raw(pattern.as_bytes()),
|
||||
key_type,
|
||||
ttl: -2,
|
||||
size: 0,
|
||||
value_preview,
|
||||
};
|
||||
return Ok(RedisScanResult { cursor: 0, keys: vec![key_info], total_keys });
|
||||
}
|
||||
Ok(false) => {
|
||||
return Ok(RedisScanResult { cursor: 0, keys: vec![], total_keys });
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut all_keys: Vec<RedisKeyInfo> = Vec::new();
|
||||
|
||||
let mut current_cursor = cursor;
|
||||
|
||||
for _ in 0..iterations {
|
||||
|
|
|
|||
Loading…
Reference in New Issue