mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-29 16:18:55 +00:00
feat(redis): implement pagination for Redis key scanning and enhance UI for loading more keys
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Search, RefreshCw, Key } from "lucide-vue-next";
|
||||
import { Search, RefreshCw, Key, Loader2 } from "lucide-vue-next";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import "splitpanes/dist/splitpanes.css";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -22,11 +22,34 @@ const keys = ref<RedisKeyInfo[]>([]);
|
||||
const loading = ref(false);
|
||||
const searchPattern = ref("*");
|
||||
const selectedKey = ref<string | null>(null);
|
||||
const cursor = ref(0);
|
||||
const hasMore = ref(false);
|
||||
|
||||
const PAGE_SIZE = 200;
|
||||
|
||||
async function loadKeys() {
|
||||
loading.value = true;
|
||||
try {
|
||||
keys.value = await api.redisScanKeys(props.connectionId, props.db, searchPattern.value, 1000);
|
||||
const result = await api.redisScanKeys(props.connectionId, props.db, 0, searchPattern.value, PAGE_SIZE);
|
||||
keys.value = result.keys;
|
||||
cursor.value = result.cursor;
|
||||
hasMore.value = result.cursor !== 0;
|
||||
selectedKey.value = null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMoreKeys() {
|
||||
if (loading.value || !hasMore.value) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await api.redisScanKeys(props.connectionId, props.db, cursor.value, searchPattern.value, PAGE_SIZE);
|
||||
const existingKeys = new Set(keys.value.map((k) => k.key));
|
||||
keys.value = [...keys.value, ...result.keys.filter((k) => !existingKeys.has(k.key))];
|
||||
cursor.value = result.cursor;
|
||||
hasMore.value = result.cursor !== 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -72,13 +95,14 @@ onMounted(loadKeys);
|
||||
@keydown.enter="loadKeys"
|
||||
/>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6 shrink-0" @click="loadKeys">
|
||||
<RefreshCw class="h-3 w-3" />
|
||||
<Loader2 v-if="loading" class="h-3 w-3 animate-spin" />
|
||||
<RefreshCw v-else class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Key count -->
|
||||
<div class="px-3 py-1 text-xs text-muted-foreground border-b shrink-0">
|
||||
{{ t('redis.keys', { count: keys.length }) }}
|
||||
{{ loading && keys.length === 0 ? t('redis.loadingKeys') : t('redis.keys', { count: keys.length }) }}
|
||||
</div>
|
||||
|
||||
<!-- Key list -->
|
||||
@@ -97,6 +121,16 @@ onMounted(loadKeys);
|
||||
<div v-if="keys.length === 0 && !loading" class="px-3 py-8 text-center text-muted-foreground text-xs">
|
||||
{{ t('redis.noKeys') }}
|
||||
</div>
|
||||
<div v-if="loading && keys.length === 0" class="px-3 py-8 flex items-center justify-center gap-2 text-muted-foreground text-xs">
|
||||
<Loader2 class="w-3.5 h-3.5 animate-spin" />
|
||||
<span>{{ t('redis.loadingKeys') }}</span>
|
||||
</div>
|
||||
<div v-if="hasMore || (loading && keys.length > 0)" class="p-2">
|
||||
<Button variant="outline" size="sm" class="w-full h-7 text-xs" :disabled="loading" @click="loadMoreKeys">
|
||||
<Loader2 v-if="loading" class="w-3 h-3 mr-1.5 animate-spin" />
|
||||
{{ t('redis.loadMoreKeys') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
|
||||
@@ -101,6 +101,7 @@ async function toggle() {
|
||||
} else if (node.type === "redis-db" && node.connectionId && node.database) {
|
||||
const tabTitle = `${connectionStore.getConfig(node.connectionId)?.name || "Redis"}:db${node.database}`;
|
||||
queryStore.createTab(node.connectionId, node.database, tabTitle, "redis");
|
||||
await connectionStore.loadRedisKeys(node.connectionId, Number(node.database), node.id);
|
||||
} else if (node.type === "mongo-db" && node.connectionId && node.database) {
|
||||
await connectionStore.loadMongoCollections(node.connectionId, node.database);
|
||||
} else if (node.type === "mongo-collection" && node.connectionId && node.database) {
|
||||
@@ -179,6 +180,8 @@ async function refresh() {
|
||||
const node = props.node;
|
||||
node.isExpanded = false;
|
||||
node.children = [];
|
||||
node.scanCursor = undefined;
|
||||
node.hasMore = undefined;
|
||||
await toggle();
|
||||
}
|
||||
|
||||
@@ -229,15 +232,23 @@ const visibleChildren = computed(() => {
|
||||
});
|
||||
|
||||
const hasMoreChildren = computed(() =>
|
||||
(props.node.children?.length ?? 0) > displayLimit.value
|
||||
(props.node.children?.length ?? 0) > displayLimit.value || !!props.node.hasMore
|
||||
);
|
||||
|
||||
const remainingCount = computed(() =>
|
||||
(props.node.children?.length ?? 0) - displayLimit.value
|
||||
);
|
||||
|
||||
function showMore() {
|
||||
displayLimit.value += CHILDREN_PAGE_SIZE;
|
||||
async function showMore() {
|
||||
if ((props.node.children?.length ?? 0) > displayLimit.value) {
|
||||
displayLimit.value += CHILDREN_PAGE_SIZE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.node.type === "redis-db" && props.node.connectionId && props.node.database && props.node.hasMore) {
|
||||
await connectionStore.loadMoreRedisKeys(props.node.connectionId, Number(props.node.database), props.node.id);
|
||||
displayLimit.value += CHILDREN_PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -269,7 +280,8 @@ function showMore() {
|
||||
:style="{ paddingLeft: `${(depth + 1) * 16 + 8}px` }"
|
||||
@click="showMore"
|
||||
>
|
||||
<span>{{ t('sidebar.showMore', { count: Math.min(CHILDREN_PAGE_SIZE, remainingCount) }) }}</span>
|
||||
<Loader2 v-if="node.isLoading" class="w-3 h-3 shrink-0 animate-spin" />
|
||||
<span>{{ node.hasMore && remainingCount <= 0 ? t('sidebar.loadMore') : t('sidebar.showMore', { count: Math.min(CHILDREN_PAGE_SIZE, remainingCount) }) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user