feat(redis): implement pagination for Redis key scanning and enhance UI for loading more keys

This commit is contained in:
t8y2 committed 2026-04-30 13:04:48 +08:00
1 parent 753b72ebe4
commit 5c546f68af
9 files changed
+137 -40

No files matched your search

+4 -3
View File
@@ -2,7 +2,7 @@ use std::sync::Arc;
use tauri::State;
use crate::commands::connection::{AppState, PoolKind};
use crate::db::redis_driver::{self, RedisKeyInfo, RedisValue};
use crate::db::redis_driver::{self, RedisScanResult, RedisValue};
#[tauri::command]
pub async fn redis_list_databases(
@@ -25,16 +25,17 @@ pub async fn redis_scan_keys(
state: State<'_, Arc<AppState>>,
connection_id: String,
db: u32,
cursor: u64,
pattern: String,
count: usize,
) -> Result<Vec<RedisKeyInfo>, String> {
) -> Result<RedisScanResult, String> {
let connections = state.connections.lock().await;
let pool = connections.get(&connection_id).ok_or("Connection not found")?;
match pool {
PoolKind::Redis(con) => {
let mut con = con.lock().await;
redis_driver::select_db(&mut con, db).await?;
redis_driver::scan_keys(&mut con, &pattern, count).await
redis_driver::scan_keys_page(&mut con, cursor, &pattern, count).await
}
_ => Err("Not a Redis connection".to_string()),
}
+32 -25
View File
@@ -8,6 +8,12 @@ pub struct RedisKeyInfo {
pub ttl: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedisScanResult {
pub cursor: u64,
pub keys: Vec<RedisKeyInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedisValue {
pub key: String,
@@ -18,13 +24,20 @@ pub struct RedisValue {
pub async fn connect(url: &str) -> Result<redis::aio::MultiplexedConnection, String> {
let client = redis::Client::open(url).map_err(|e| format!("Redis connection failed: {e}"))?;
tokio::time::timeout(
let mut con = tokio::time::timeout(
std::time::Duration::from_secs(10),
client.get_multiplexed_async_connection(),
)
.await
.map_err(|_| "Redis connection timed out (10s)".to_string())?
.map_err(|e| format!("Redis connection failed: {e}"))
.map_err(|e| format!("Redis connection failed: {e}"))?;
redis::cmd("PING")
.query_async::<String>(&mut con)
.await
.map_err(|e| format!("Redis authentication failed or command rejected: {e}"))?;
Ok(con)
}
pub async fn list_databases(con: &mut redis::aio::MultiplexedConnection) -> Result<Vec<u32>, String> {
@@ -58,33 +71,24 @@ pub async fn select_db(con: &mut redis::aio::MultiplexedConnection, db: u32) ->
.map_err(|e| e.to_string())
}
pub async fn scan_keys(
pub async fn scan_keys_page(
con: &mut redis::aio::MultiplexedConnection,
cursor: u64,
pattern: &str,
count: usize,
) -> Result<Vec<RedisKeyInfo>, String> {
let mut cursor: u64 = 0;
let mut all_keys: Vec<String> = Vec::new();
loop {
let (new_cursor, keys): (u64, Vec<String>) = redis::cmd("SCAN")
.arg(cursor)
.arg("MATCH")
.arg(pattern)
.arg("COUNT")
.arg(100)
.query_async(con)
.await
.map_err(|e| e.to_string())?;
all_keys.extend(keys);
cursor = new_cursor;
if cursor == 0 || all_keys.len() >= count {
break;
}
}
all_keys.truncate(count);
) -> Result<RedisScanResult, String> {
let (next_cursor, keys): (u64, Vec<String>) = redis::cmd("SCAN")
.arg(cursor)
.arg("MATCH")
.arg(pattern)
.arg("COUNT")
.arg(count)
.query_async(con)
.await
.map_err(|e| e.to_string())?;
let mut result = Vec::new();
for key in &all_keys {
for key in &keys {
let key_type: String = redis::cmd("TYPE")
.arg(key.as_str())
.query_async(con)
@@ -99,7 +103,10 @@ pub async fn scan_keys(
ttl,
});
}
Ok(result)
Ok(RedisScanResult {
cursor: next_cursor,
keys: result,
})
}
pub async fn get_value(