Add Redis cluster support and improve key loading
This commit is contained in:
1 parent
66ce9a368e
commit
797c0d8d21
20 files changed
+932
-201
No files matched your search
@@ -105,6 +105,7 @@ const defaultForm = (): Omit<ConnectionConfig, "id"> => ({
|
||||
redis_sentinel_username: "",
|
||||
redis_sentinel_password: "",
|
||||
redis_sentinel_tls: false,
|
||||
redis_cluster_nodes: "",
|
||||
});
|
||||
|
||||
const form = ref(defaultForm());
|
||||
@@ -362,6 +363,7 @@ watch(
|
||||
redis_sentinel_username: config.redis_sentinel_username || "",
|
||||
redis_sentinel_password: config.redis_sentinel_password || "",
|
||||
redis_sentinel_tls: config.redis_sentinel_tls || false,
|
||||
redis_cluster_nodes: config.redis_cluster_nodes || "",
|
||||
};
|
||||
selectedType.value = profile;
|
||||
mongoUseUrl.value = !!config.connection_string;
|
||||
@@ -753,15 +755,29 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
||||
config.redis_sentinel_username = undefined;
|
||||
config.redis_sentinel_password = undefined;
|
||||
config.redis_sentinel_tls = undefined;
|
||||
config.redis_cluster_nodes = undefined;
|
||||
} else if (config.redis_connection_mode === "sentinel") {
|
||||
config.redis_sentinel_master = config.redis_sentinel_master?.trim() || "";
|
||||
config.redis_sentinel_nodes = normalizeRedisSentinelNodes(config.redis_sentinel_nodes || "");
|
||||
config.redis_sentinel_username = config.redis_sentinel_username?.trim() || "";
|
||||
config.redis_cluster_nodes = undefined;
|
||||
const firstNode = firstRedisSentinelEndpoint(config.redis_sentinel_nodes);
|
||||
if (firstNode) {
|
||||
config.host = firstNode.host;
|
||||
config.port = firstNode.port;
|
||||
}
|
||||
} else if (config.redis_connection_mode === "cluster") {
|
||||
config.redis_sentinel_master = undefined;
|
||||
config.redis_sentinel_nodes = undefined;
|
||||
config.redis_sentinel_username = undefined;
|
||||
config.redis_sentinel_password = undefined;
|
||||
config.redis_sentinel_tls = undefined;
|
||||
config.redis_cluster_nodes = normalizeRedisClusterNodes(config.redis_cluster_nodes || "");
|
||||
const firstNode = firstRedisClusterEndpoint(config.redis_cluster_nodes);
|
||||
if (firstNode) {
|
||||
config.host = firstNode.host;
|
||||
config.port = firstNode.port;
|
||||
}
|
||||
} else {
|
||||
config.redis_connection_mode = "standalone";
|
||||
config.redis_sentinel_master = undefined;
|
||||
@@ -769,6 +785,7 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
||||
config.redis_sentinel_username = undefined;
|
||||
config.redis_sentinel_password = undefined;
|
||||
config.redis_sentinel_tls = undefined;
|
||||
config.redis_cluster_nodes = undefined;
|
||||
}
|
||||
if (config.db_type !== "mysql" && config.db_type !== "clickhouse") {
|
||||
config.ca_cert_path = undefined;
|
||||
@@ -875,6 +892,14 @@ function normalizePostgresSslMode(value: string): string {
|
||||
}
|
||||
|
||||
function normalizeRedisSentinelNodes(value: string): string {
|
||||
return normalizeRedisNodeList(value);
|
||||
}
|
||||
|
||||
function normalizeRedisClusterNodes(value: string): string {
|
||||
return normalizeRedisNodeList(value);
|
||||
}
|
||||
|
||||
function normalizeRedisNodeList(value: string): string {
|
||||
return value
|
||||
.split(/[\n,;]+/)
|
||||
.map((node) => node.trim())
|
||||
@@ -883,13 +908,21 @@ function normalizeRedisSentinelNodes(value: string): string {
|
||||
}
|
||||
|
||||
function firstRedisSentinelEndpoint(value?: string): { host: string; port: number } | null {
|
||||
const first = normalizeRedisSentinelNodes(value || "")
|
||||
const first = normalizeRedisNodeList(value || "")
|
||||
.split("\n")
|
||||
.find(Boolean);
|
||||
if (!first) return null;
|
||||
return parseRedisEndpoint(first, 26379);
|
||||
}
|
||||
|
||||
function firstRedisClusterEndpoint(value?: string): { host: string; port: number } | null {
|
||||
const first = normalizeRedisNodeList(value || "")
|
||||
.split("\n")
|
||||
.find(Boolean);
|
||||
if (!first) return null;
|
||||
return parseRedisEndpoint(first, 6379);
|
||||
}
|
||||
|
||||
function parseRedisEndpoint(value: string, defaultPort: number): { host: string; port: number } {
|
||||
const endpoint = value
|
||||
.trim()
|
||||
@@ -1616,7 +1649,7 @@ function openExternalUrl(url: string) {
|
||||
<div class="col-span-3 flex gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
:variant="form.redis_connection_mode === 'sentinel' ? 'outline' : 'default'"
|
||||
:variant="form.redis_connection_mode === 'standalone' ? 'default' : 'outline'"
|
||||
@click="form.redis_connection_mode = 'standalone'"
|
||||
>
|
||||
{{ t("connection.redisStandaloneMode") }}
|
||||
@@ -1628,13 +1661,22 @@ function openExternalUrl(url: string) {
|
||||
>
|
||||
{{ t("connection.redisSentinelMode") }}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
:variant="form.redis_connection_mode === 'cluster' ? 'default' : 'outline'"
|
||||
@click="form.redis_connection_mode = 'cluster'"
|
||||
>
|
||||
{{ t("connection.redisClusterMode") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{
|
||||
form.redis_connection_mode === "sentinel"
|
||||
? t("connection.redisFirstSentinel")
|
||||
: t("connection.host")
|
||||
: form.redis_connection_mode === "cluster"
|
||||
? t("connection.redisFirstClusterNode")
|
||||
: t("connection.host")
|
||||
}}</Label>
|
||||
<Input v-model="form.host" class="col-span-2" />
|
||||
<Input v-model.number="form.port" type="number" class="col-span-1" />
|
||||
@@ -1669,6 +1711,17 @@ function openExternalUrl(url: string) {
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="form.redis_connection_mode === 'cluster'">
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<Label class="text-right mt-2">{{ t("connection.redisClusterNodes") }}</Label>
|
||||
<textarea
|
||||
v-model="form.redis_cluster_nodes"
|
||||
class="col-span-3 flex min-h-[76px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
placeholder="redis-1:6379 redis-2:6379"
|
||||
spellcheck="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.user") }}</Label>
|
||||
<Input v-model="form.username" class="col-span-3" placeholder="default" />
|
||||
|
||||
@@ -196,6 +196,25 @@ async function streamValueSearch(requestId: number) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fillInitialKeyBatch(requestId: number) {
|
||||
const targetCount = Math.max(1, settingsStore.editorSettings.redisScanPageSize);
|
||||
let rounds = 0;
|
||||
while (
|
||||
requestId === searchRequestId &&
|
||||
searchMode.value === "key" &&
|
||||
hasMore.value &&
|
||||
flatKeys.value.length < targetCount
|
||||
) {
|
||||
const beforeCount = flatKeys.value.length;
|
||||
const applied = await scanNextPage(requestId);
|
||||
if (!applied) return;
|
||||
rounds += 1;
|
||||
if (flatKeys.value.length >= targetCount) return;
|
||||
if (rounds >= 12 && flatKeys.value.length === beforeCount) return;
|
||||
if (rounds >= 24) return;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadKeys() {
|
||||
const requestId = ++searchRequestId;
|
||||
loading.value = true;
|
||||
@@ -211,8 +230,12 @@ async function loadKeys() {
|
||||
return;
|
||||
}
|
||||
const applied = await scanNextPage(requestId);
|
||||
if (applied && searchMode.value === "value") {
|
||||
await streamValueSearch(requestId);
|
||||
if (applied) {
|
||||
if (searchMode.value === "value") {
|
||||
await streamValueSearch(requestId);
|
||||
} else {
|
||||
await fillInitialKeyBatch(requestId);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (requestId === searchRequestId) {
|
||||
|
||||
@@ -162,8 +162,11 @@ export default {
|
||||
modeForm: "Form",
|
||||
redisStandaloneMode: "Standalone",
|
||||
redisSentinelMode: "Sentinel",
|
||||
redisClusterMode: "Cluster",
|
||||
redisFirstSentinel: "First Sentinel",
|
||||
redisFirstClusterNode: "First Seed Node",
|
||||
redisSentinelNodes: "Sentinel Nodes",
|
||||
redisClusterNodes: "Cluster Seed Nodes",
|
||||
redisSentinelMaster: "Master Name",
|
||||
redisSentinelUser: "Sentinel User",
|
||||
redisSentinelPassword: "Sentinel Password",
|
||||
|
||||
@@ -154,8 +154,11 @@ export default {
|
||||
modeForm: "Formulario",
|
||||
redisStandaloneMode: "Directa",
|
||||
redisSentinelMode: "Sentinel",
|
||||
redisClusterMode: "Cluster",
|
||||
redisFirstSentinel: "Primer Sentinel",
|
||||
redisFirstClusterNode: "Primer nodo semilla",
|
||||
redisSentinelNodes: "Nodos Sentinel",
|
||||
redisClusterNodes: "Nodos semilla del cluster",
|
||||
redisSentinelMaster: "Nombre del master",
|
||||
redisSentinelUser: "Usuario Sentinel",
|
||||
redisSentinelPassword: "Contraseña Sentinel",
|
||||
|
||||
@@ -159,8 +159,11 @@ export default {
|
||||
modeForm: "表单",
|
||||
redisStandaloneMode: "直连",
|
||||
redisSentinelMode: "哨兵",
|
||||
redisClusterMode: "集群",
|
||||
redisFirstSentinel: "首个哨兵",
|
||||
redisFirstClusterNode: "首个种子节点",
|
||||
redisSentinelNodes: "哨兵节点",
|
||||
redisClusterNodes: "集群种子节点",
|
||||
redisSentinelMaster: "Master 名称",
|
||||
redisSentinelUser: "哨兵用户名",
|
||||
redisSentinelPassword: "哨兵密码",
|
||||
|
||||
@@ -87,12 +87,13 @@ export interface ConnectionConfig {
|
||||
connection_string?: string;
|
||||
jdbc_driver_class?: string;
|
||||
jdbc_driver_paths?: string[];
|
||||
redis_connection_mode?: "standalone" | "sentinel";
|
||||
redis_connection_mode?: "standalone" | "sentinel" | "cluster";
|
||||
redis_sentinel_master?: string;
|
||||
redis_sentinel_nodes?: string;
|
||||
redis_sentinel_username?: string;
|
||||
redis_sentinel_password?: string;
|
||||
redis_sentinel_tls?: boolean;
|
||||
redis_cluster_nodes?: string;
|
||||
one_time?: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user