feat: CLI MULTI/EXEC transaction support
- CliView: detect MULTI/EXEC/DISCARD commands - MULTI enters transaction mode (tx> prompt, yellow) - Commands in tx mode are queued (QUEUED + queue count) - EXEC executes all queued commands, shows array results - DISCARD cancels transaction - WATCH/UNWATCH handled as non-queued tx commands - i18n keys for tx messages (zh-CN + en)
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@
|
||||
- [x] **Monaco 编辑器** — 替换 StringEditor 的 textarea,支持 JSON 折叠/展开/语法高亮
|
||||
- [x] **Redis 命令自动补全** — CLI 输入时智能提示(基于 commands.js 命令数据库)
|
||||
- [x] **CLI SUBSCRIBE/MONITOR** — 实时消息流 + MONITOR 命令流 + 停止按钮
|
||||
- [ ] **CLI MULTI/EXEC** — 事务模式支持
|
||||
- [x] **CLI MULTI/EXEC** — 事务模式支持
|
||||
- [ ] **Stream 消费者组** — XINFO GROUPS / XINFO CONSUMERS 展开表格
|
||||
|
||||
## P2 — 重要功能增强
|
||||
|
||||
@@ -15,6 +15,8 @@ const outputRef = ref<HTMLDivElement>()
|
||||
const suggestionIndex = ref(-1)
|
||||
const inputRef = ref<HTMLInputElement>()
|
||||
const streamingMode = ref<'none' | 'subscribe' | 'monitor'>('none')
|
||||
const txMode = ref(false)
|
||||
const txQueue = ref<string[]>([])
|
||||
|
||||
const showSuggestions = computed(() => {
|
||||
return suggestions.value.length > 0 && !input.value.includes(' ')
|
||||
@@ -48,6 +50,57 @@ async function execute() {
|
||||
const args = parts.slice(1)
|
||||
|
||||
try {
|
||||
// Handle MULTI
|
||||
if (command === 'MULTI' && !txMode.value) {
|
||||
const result = await window.electronAPI.redis.execute(connStore.activeId, command, args)
|
||||
if (result === 'OK') {
|
||||
txMode.value = true
|
||||
txQueue.value = []
|
||||
history.value.push({ cmd, result: t('cli.txStarted') })
|
||||
} else {
|
||||
history.value.push({ cmd, result: String(result), error: true })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle EXEC (end transaction)
|
||||
if (command === 'EXEC' && txMode.value) {
|
||||
const result = await window.electronAPI.redis.execute(connStore.activeId, command, args)
|
||||
let formatted = ''
|
||||
if (Array.isArray(result)) {
|
||||
formatted = result.map((item: any, i: number) => {
|
||||
if (item === null) return `${i + 1}) (nil)`
|
||||
if (typeof item === 'number') return `${i + 1}) (integer) ${item}`
|
||||
if (item instanceof Error) return `${i + 1}) (error) ${item.message}`
|
||||
return `${i + 1}) "${item}"`
|
||||
}).join('\n')
|
||||
} else {
|
||||
formatted = String(result)
|
||||
}
|
||||
history.value.push({ cmd: `EXEC (${txQueue.value.length} commands)`, result: formatted })
|
||||
txMode.value = false
|
||||
txQueue.value = []
|
||||
return
|
||||
}
|
||||
|
||||
// Handle DISCARD (cancel transaction)
|
||||
if (command === 'DISCARD' && txMode.value) {
|
||||
await window.electronAPI.redis.execute(connStore.activeId, command, args)
|
||||
txMode.value = false
|
||||
txQueue.value = []
|
||||
history.value.push({ cmd, result: t('cli.txDiscarded') })
|
||||
return
|
||||
}
|
||||
|
||||
// In transaction mode: queue command
|
||||
if (txMode.value && !['MULTI', 'EXEC', 'DISCARD', 'WATCH', 'UNWATCH'].includes(command)) {
|
||||
const result = await window.electronAPI.redis.execute(connStore.activeId, command, args)
|
||||
txQueue.value.push(cmd)
|
||||
const queueInfo = t('cli.txQueue', { n: txQueue.value.length })
|
||||
history.value.push({ cmd, result: `${t('cli.txQueued')} — ${queueInfo}` })
|
||||
return
|
||||
}
|
||||
|
||||
// Handle SUBSCRIBE / PSUBSCRIBE
|
||||
if (command === 'SUBSCRIBE' || command === 'PSUBSCRIBE') {
|
||||
const channels = args.filter(a => !a.startsWith('"'))
|
||||
@@ -232,13 +285,13 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="cli-input-bar">
|
||||
<template v-if="streamingMode === 'none'">
|
||||
<span class="prompt">></span>
|
||||
<span class="prompt" :class="{ tx: txMode }">{{ txMode ? t('cli.txPrompt') : '>' }}</span>
|
||||
<div class="cli-input-wrapper">
|
||||
<input
|
||||
ref="inputRef"
|
||||
v-model="input"
|
||||
class="cli-input"
|
||||
:placeholder="t('cli.placeholder')"
|
||||
:placeholder="txMode ? t('cli.txQueue', { n: txQueue.length }) : t('cli.placeholder')"
|
||||
@keydown="onKeydown"
|
||||
autofocus
|
||||
/>
|
||||
@@ -303,6 +356,11 @@ onUnmounted(() => {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.prompt.tx {
|
||||
color: var(--warning);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cli-result {
|
||||
color: var(--text-secondary);
|
||||
margin: 4px 0 0 18px;
|
||||
|
||||
@@ -142,6 +142,12 @@ export default {
|
||||
subscribed: 'Subscribed, waiting for messages...',
|
||||
monitoring: 'Monitoring, waiting for commands...',
|
||||
subscribedTo: 'Subscribed to: {channels}',
|
||||
txStarted: 'Transaction started, commands are queued...',
|
||||
txQueued: 'QUEUED',
|
||||
txExecuted: 'Transaction executed',
|
||||
txDiscarded: 'Transaction discarded',
|
||||
txQueue: 'Queue ({n})',
|
||||
txPrompt: 'tx>',
|
||||
},
|
||||
slowlog: {
|
||||
title: 'Slow Log',
|
||||
|
||||
@@ -142,6 +142,12 @@ export default {
|
||||
subscribed: '已订阅,等待消息...',
|
||||
monitoring: '监控中,等待命令...',
|
||||
subscribedTo: '已订阅频道: {channels}',
|
||||
txStarted: '已开启事务,输入命令入队...',
|
||||
txQueued: '已入队',
|
||||
txExecuted: '事务已执行',
|
||||
txDiscarded: '事务已取消',
|
||||
txQueue: '队列 ({n})',
|
||||
txPrompt: 'tx>',
|
||||
},
|
||||
slowlog: {
|
||||
title: '慢日志',
|
||||
|
||||
Reference in New Issue
Block a user