mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-23 13:51:31 +00:00
feat(redis): add copy insert statement button for Redis key value migration
Generates a Redis insert command (SET/RPUSH/SADD/ZADD/HSET/XADD/JSON.SET) from the current key's value and copies it to clipboard. Supports TTL, pagination hints, and binary value handling.
This commit is contained in:
@@ -2,7 +2,20 @@
|
||||
import { computed, ref, onBeforeUnmount, onMounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { DynamicScroller, DynamicScrollerItem, RecycleScroller } from "vue-virtual-scroller";
|
||||
import { Braces, Copy, Eye, FileText, Trash2, Save, RefreshCw, Plus, Loader2, Pencil, WrapText } from "@lucide/vue";
|
||||
import {
|
||||
Braces,
|
||||
Copy,
|
||||
Eye,
|
||||
FileText,
|
||||
Terminal,
|
||||
Trash2,
|
||||
Save,
|
||||
RefreshCw,
|
||||
Plus,
|
||||
Loader2,
|
||||
Pencil,
|
||||
WrapText,
|
||||
} from "@lucide/vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -298,6 +311,94 @@ async function copyText(text: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRedisArg(val: string): string {
|
||||
return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
function generateInsertStatements(): string | null {
|
||||
if (!data.value) return null;
|
||||
if (data.value.value_is_binary) return null;
|
||||
|
||||
const key = data.value.key_display;
|
||||
const type = data.value.key_type;
|
||||
const commands: string[] = [];
|
||||
|
||||
const isCollection = ["list", "set", "zset", "hash"].includes(type);
|
||||
if (isCollection) {
|
||||
const total = data.value.total;
|
||||
const loaded = collectionItems.value.length;
|
||||
if (total != null && total > loaded) {
|
||||
commands.push(`-- Note: Only ${loaded} of ${total} items included`);
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "string":
|
||||
commands.push(`SET ${escapeRedisArg(key)} ${escapeRedisArg(String(data.value.value))}`);
|
||||
break;
|
||||
case "list": {
|
||||
const items = collectionItems.value.map((v) => escapeRedisArg(String(v))).join(" ");
|
||||
commands.push(`RPUSH ${escapeRedisArg(key)} ${items}`);
|
||||
break;
|
||||
}
|
||||
case "set": {
|
||||
const members = collectionItems.value.map((v) => escapeRedisArg(String(v))).join(" ");
|
||||
commands.push(`SADD ${escapeRedisArg(key)} ${members}`);
|
||||
break;
|
||||
}
|
||||
case "zset": {
|
||||
const pairs = collectionItems.value.map((v) => `${v.score} ${escapeRedisArg(String(v.member))}`).join(" ");
|
||||
commands.push(`ZADD ${escapeRedisArg(key)} ${pairs}`);
|
||||
break;
|
||||
}
|
||||
case "hash": {
|
||||
const pairs = collectionItems.value
|
||||
.map((v) => `${escapeRedisArg(String(v.field))} ${escapeRedisArg(String(v.value))}`)
|
||||
.join(" ");
|
||||
commands.push(`HSET ${escapeRedisArg(key)} ${pairs}`);
|
||||
break;
|
||||
}
|
||||
case "stream": {
|
||||
const entries = Array.isArray(data.value.value) ? data.value.value : [];
|
||||
for (const entry of entries) {
|
||||
const fields = Object.entries((entry as any).fields ?? {})
|
||||
.map(([f, v]) => `${escapeRedisArg(f)} ${escapeRedisArg(String(v))}`)
|
||||
.join(" ");
|
||||
commands.push(`XADD ${escapeRedisArg(key)} * ${fields}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (type === "ReJSON-RL" || type === "JSON") {
|
||||
const json = JSON.stringify(data.value.value);
|
||||
commands.push(`JSON.SET ${escapeRedisArg(key)} $ '${json}'`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.value.ttl > 0) {
|
||||
commands.push(`EXPIRE ${escapeRedisArg(key)} ${data.value.ttl}`);
|
||||
}
|
||||
|
||||
return commands.join("\n");
|
||||
}
|
||||
|
||||
async function copyInsertStatement() {
|
||||
if (!data.value) return;
|
||||
if (data.value.value_is_binary) {
|
||||
toast(t("redis.copyInsertStatementBinary"), 3000);
|
||||
return;
|
||||
}
|
||||
const stmt = generateInsertStatements();
|
||||
if (!stmt) return;
|
||||
try {
|
||||
await copyToClipboard(stmt);
|
||||
toast(t("redis.copyInsertStatement"), 2000);
|
||||
} catch (e: any) {
|
||||
toast(t("grid.copyFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function copyMember(value: unknown) {
|
||||
void copyText(formatRedisMemberDetail(value).text);
|
||||
}
|
||||
@@ -623,6 +724,14 @@ onBeforeUnmount(() => {
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7 shrink-0" @click="copyValue"
|
||||
><Copy class="h-3.5 w-3.5"
|
||||
/></Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7 shrink-0"
|
||||
:title="t('redis.copyInsertStatement')"
|
||||
@click="copyInsertStatement"
|
||||
><Terminal class="h-3.5 w-3.5"
|
||||
/></Button>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7 shrink-0 text-destructive" @click="requestDeleteKey"
|
||||
><Trash2 class="h-3.5 w-3.5"
|
||||
/></Button>
|
||||
|
||||
@@ -1295,6 +1295,8 @@ export default {
|
||||
viewMember: "View full value",
|
||||
copyMember: "Copy member",
|
||||
copied: "Copied",
|
||||
copyInsertStatement: "Copy Insert Statement",
|
||||
copyInsertStatementBinary: "Cannot generate insert statement for binary data",
|
||||
editMember: "Edit",
|
||||
jsonView: "JSON view",
|
||||
rawContent: "Raw content",
|
||||
|
||||
@@ -1270,6 +1270,8 @@ export default {
|
||||
viewMember: "查看完整内容",
|
||||
copyMember: "复制成员",
|
||||
copied: "已复制",
|
||||
copyInsertStatement: "复制插入语句",
|
||||
copyInsertStatementBinary: "二进制数据无法生成插入语句",
|
||||
editMember: "编辑",
|
||||
jsonView: "JSON 视图",
|
||||
rawContent: "原始内容",
|
||||
|
||||
Reference in New Issue
Block a user