fix: i18n, password edit, and key count issues

- Add i18n to all components (zh-CN/en)
- Fix password not showing when editing connection
- Fix key count showing 0 by adding getInfo function
This commit is contained in:
2026-07-04 22:26:32 +08:00
parent 86b0eff95b
commit 3b402771d5
13 changed files with 155 additions and 100 deletions
+14 -12
View File
@@ -1,12 +1,14 @@
<script setup lang="ts">
// src/renderer/src/components/HashEditor.vue
// src/components/HashEditor.vue
import { ref, watch } from 'vue'
import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
const keyStore = useKeyStore()
const connStore = useConnectionStore()
const { t } = useI18n()
interface HashField {
field: string
@@ -81,43 +83,43 @@ async function addField() {
<div class="hash-editor">
<div class="editor-toolbar">
<span class="type-badge">HASH</span>
<el-button size="small" type="primary" @click="showAddDialog = true">Add Field</el-button>
<el-button size="small" @click="refresh" :loading="loading">Refresh</el-button>
<el-button size="small" type="primary" @click="showAddDialog = true">{{ t('editor.addField') }}</el-button>
<el-button size="small" @click="refresh" :loading="loading">{{ t('common.refresh') }}</el-button>
</div>
<div class="hash-table-wrap">
<el-table :data="fields" size="small" class="hash-table" max-height="400">
<el-table-column prop="field" label="Field" min-width="150">
<el-table-column prop="field" :label="t('editor.field')" min-width="150">
<template #default="{ row }">
<el-input v-model="row.field" size="small" class="hash-input" />
</template>
</el-table-column>
<el-table-column prop="value" label="Value" min-width="250">
<el-table-column prop="value" :label="t('editor.value')" min-width="250">
<template #default="{ row }">
<el-input v-model="row.value" size="small" class="hash-input" />
</template>
</el-table-column>
<el-table-column width="120">
<template #default="{ row }">
<el-button size="small" type="primary" text @click="saveField(row)">Save</el-button>
<el-button size="small" type="danger" text @click="deleteField(row)">Del</el-button>
<el-button size="small" type="primary" text @click="saveField(row)">{{ t('editor.save') }}</el-button>
<el-button size="small" type="danger" text @click="deleteField(row)">{{ t('common.delete') }}</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog v-model="showAddDialog" title="Add Hash Field" width="400px">
<el-dialog v-model="showAddDialog" :title="t('editor.addField')" width="400px">
<el-form label-position="top">
<el-form-item label="Field">
<el-form-item :label="t('editor.field')">
<el-input v-model="newFieldName" />
</el-form-item>
<el-form-item label="Value">
<el-form-item :label="t('editor.value')">
<el-input v-model="newFieldValue" type="textarea" :rows="4" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">Cancel</el-button>
<el-button type="primary" @click="addField">Add</el-button>
<el-button @click="showAddDialog = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="addField">{{ t('common.add') }}</el-button>
</template>
</el-dialog>
</div>
+16 -14
View File
@@ -1,8 +1,9 @@
<script setup lang="ts">
// src/renderer/src/components/KeyDetail.vue
// src/components/KeyDetail.vue
import { computed, ref } from 'vue'
import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
import StringEditor from './StringEditor.vue'
import HashEditor from './HashEditor.vue'
@@ -13,6 +14,7 @@ import StreamEditor from './StreamEditor.vue'
const keyStore = useKeyStore()
const connStore = useConnectionStore()
const { t } = useI18n()
const showTtlDialog = ref(false)
const ttlValue = ref(0)
@@ -133,9 +135,9 @@ async function refreshKey() {
</el-button>
</div>
<div class="key-actions">
<el-button size="small" @click="openRenameDialog">Rename</el-button>
<el-button size="small" @click="refreshKey">Refresh</el-button>
<el-button size="small" type="danger" @click="deleteKey">Delete</el-button>
<el-button size="small" @click="openRenameDialog">{{ t('key.rename') }}</el-button>
<el-button size="small" @click="refreshKey">{{ t('common.refresh') }}</el-button>
<el-button size="small" type="danger" @click="deleteKey">{{ t('common.delete') }}</el-button>
</div>
</div>
@@ -148,38 +150,38 @@ async function refreshKey() {
<StreamEditor v-else-if="keyStore.selectedType === 'stream'" />
<div v-else class="unsupported">
<el-icon><Warning /></el-icon>
<p>Type "{{ keyStore.selectedType }}" viewer not yet implemented</p>
<p>{{ t('key.unsupported', { type: keyStore.selectedType }) }}</p>
</div>
</div>
<!-- TTL Dialog -->
<el-dialog v-model="showTtlDialog" title="Set TTL" width="320px">
<el-dialog v-model="showTtlDialog" :title="t('key.ttlTitle')" width="320px">
<el-form label-position="top">
<el-form-item label="Seconds (-1 = no expiry, 0 = expire now)">
<el-form-item :label="t('key.ttlHint')">
<el-input-number v-model="ttlValue" :min="-1" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showTtlDialog = false">Cancel</el-button>
<el-button type="primary" @click="saveTtl">Save</el-button>
<el-button @click="showTtlDialog = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="saveTtl">{{ t('common.save') }}</el-button>
</template>
</el-dialog>
<!-- Rename Dialog -->
<el-dialog v-model="showRenameDialog" title="Rename Key" width="400px">
<el-form-item label="New name">
<el-dialog v-model="showRenameDialog" :title="t('key.renameTitle')" width="400px">
<el-form-item :label="t('key.newName')">
<el-input v-model="newName" />
</el-form-item>
<template #footer>
<el-button @click="showRenameDialog = false">Cancel</el-button>
<el-button type="primary" @click="saveRename">Rename</el-button>
<el-button @click="showRenameDialog = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="saveRename">{{ t('key.rename') }}</el-button>
</template>
</el-dialog>
</div>
<div v-else class="no-selection">
<el-icon size="48" color="var(--text-muted)"><Document /></el-icon>
<p>Select a key to view details</p>
<p>{{ t('key.selectKey') }}</p>
</div>
</template>
+15 -13
View File
@@ -1,12 +1,14 @@
<script setup lang="ts">
// src/renderer/src/components/ListEditor.vue
// src/components/ListEditor.vue
import { ref, watch } from 'vue'
import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
const keyStore = useKeyStore()
const connStore = useConnectionStore()
const { t } = useI18n()
const items = ref<string[]>([])
const loading = ref(false)
@@ -98,9 +100,9 @@ function nextPage() {
<div class="list-editor">
<div class="editor-toolbar">
<span class="type-badge">LIST</span>
<span class="item-count">{{ totalCount }} elements</span>
<el-button size="small" type="primary" @click="showAddDialog = true">Push</el-button>
<el-button size="small" @click="loadList" :loading="loading">Refresh</el-button>
<span class="item-count">{{ t('editor.elements', { n: totalCount }) }}</span>
<el-button size="small" type="primary" @click="showAddDialog = true">{{ t('editor.push') }}</el-button>
<el-button size="small" @click="loadList" :loading="loading">{{ t('common.refresh') }}</el-button>
</div>
<div class="list-content">
@@ -108,18 +110,18 @@ function nextPage() {
<span class="item-index">{{ pageStart + index }}</span>
<template v-if="editingIndex === index">
<el-input v-model="editingValue" size="small" class="item-input" />
<el-button size="small" type="primary" text @click="saveEdit">Save</el-button>
<el-button size="small" @click="editingIndex = null">Cancel</el-button>
<el-button size="small" type="primary" text @click="saveEdit">{{ t('editor.save') }}</el-button>
<el-button size="small" @click="editingIndex = null">{{ t('common.cancel') }}</el-button>
</template>
<template v-else>
<span class="item-value">{{ item }}</span>
<div class="item-actions">
<el-button size="small" text @click="startEdit(index)">Edit</el-button>
<el-button size="small" type="danger" text @click="removeItem(item)">Del</el-button>
<el-button size="small" text @click="startEdit(index)">{{ t('editor.edit') }}</el-button>
<el-button size="small" type="danger" text @click="removeItem(item)">{{ t('common.delete') }}</el-button>
</div>
</template>
</div>
<div v-if="items.length === 0 && !loading" class="empty">Empty list</div>
<div v-if="items.length === 0 && !loading" class="empty">{{ t('editor.empty') }}</div>
</div>
<div class="list-pagination" v-if="totalCount > pageSize">
@@ -128,13 +130,13 @@ function nextPage() {
<el-button size="small" :disabled="pageStart + pageSize >= totalCount" @click="nextPage">Next</el-button>
</div>
<el-dialog v-model="showAddDialog" title="Push Element" width="400px">
<el-form-item label="Value">
<el-dialog v-model="showAddDialog" :title="t('editor.push')" width="400px">
<el-form-item :label="t('editor.value')">
<el-input v-model="newItemValue" type="textarea" :rows="4" />
</el-form-item>
<template #footer>
<el-button @click="showAddDialog = false">Cancel</el-button>
<el-button type="primary" @click="addItem">Push</el-button>
<el-button @click="showAddDialog = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="addItem">{{ t('editor.push') }}</el-button>
</template>
</el-dialog>
</div>
+9 -7
View File
@@ -1,7 +1,8 @@
<script setup lang="ts">
// src/renderer/src/components/MainArea.vue
// src/components/MainArea.vue
import { ref, watch } from 'vue'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import StatusView from './StatusView.vue'
import KeyBrowser from './KeyBrowser.vue'
import CliView from './CliView.vue'
@@ -9,6 +10,7 @@ import SlowLogView from './SlowLogView.vue'
import SettingsView from './SettingsView.vue'
const connStore = useConnectionStore()
const { t } = useI18n()
type Tab = 'status' | 'keys' | 'cli' | 'slowlog' | 'settings'
const activeTab = ref<Tab>('status')
@@ -32,7 +34,7 @@ watch(
@click="activeTab = 'status'"
>
<el-icon><Monitor /></el-icon>
Status
{{ t('status.title') }}
</button>
<button
class="tab-item"
@@ -40,7 +42,7 @@ watch(
@click="activeTab = 'keys'"
>
<el-icon><Document /></el-icon>
Keys
{{ t('key.title') }}
</button>
<button
class="tab-item"
@@ -48,7 +50,7 @@ watch(
@click="activeTab = 'cli'"
>
<el-icon><Monitor /></el-icon>
CLI
{{ t('cli.title') }}
</button>
<button
class="tab-item"
@@ -56,7 +58,7 @@ watch(
@click="activeTab = 'slowlog'"
>
<el-icon><Clock /></el-icon>
Slow Log
{{ t('slowlog.title') }}
</button>
<button
class="tab-item"
@@ -64,7 +66,7 @@ watch(
@click="activeTab = 'settings'"
>
<el-icon><Setting /></el-icon>
Settings
{{ t('settings.title') }}
</button>
</div>
<div class="tab-content">
@@ -79,7 +81,7 @@ watch(
<div v-else class="no-connection">
<div class="placeholder-icon">JRD</div>
<h2>JRedisDesktop</h2>
<p>Select or create a connection to get started</p>
<p>{{ t('app.placeholder') }}</p>
</div>
</template>
+3 -2
View File
@@ -23,15 +23,16 @@ const saving = ref(false)
const testing = ref(false)
const isEdit = ref(false)
watch(() => props.visible, (v) => {
watch(() => props.visible, async (v) => {
if (v) {
if (props.connection) {
isEdit.value = true
const decryptedAuth = await connStore.decryptPassword(props.connection.auth || '')
Object.assign(form, {
name: props.connection.name,
host: props.connection.host,
port: props.connection.port,
auth: '',
auth: decryptedAuth,
username: props.connection.username,
tls: props.connection.tls,
separator: props.connection.separator,
+13 -11
View File
@@ -1,12 +1,14 @@
<script setup lang="ts">
// src/renderer/src/components/SetEditor.vue
// src/components/SetEditor.vue
import { ref, watch } from 'vue'
import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
const keyStore = useKeyStore()
const connStore = useConnectionStore()
const { t } = useI18n()
const members = ref<string[]>([])
const loading = ref(false)
@@ -69,27 +71,27 @@ watch([members, filterText], () => {
<div class="set-editor">
<div class="editor-toolbar">
<span class="type-badge">SET</span>
<span class="item-count">{{ members.length }} members</span>
<el-input v-model="filterText" placeholder="Filter..." size="small" clearable style="width: 150px" />
<el-button size="small" type="primary" @click="showAddDialog = true">Add</el-button>
<el-button size="small" @click="loadMembers" :loading="loading">Refresh</el-button>
<span class="item-count">{{ t('editor.members', { n: members.length }) }}</span>
<el-input v-model="filterText" :placeholder="t('editor.filter')" size="small" clearable style="width: 150px" />
<el-button size="small" type="primary" @click="showAddDialog = true">{{ t('common.add') }}</el-button>
<el-button size="small" @click="loadMembers" :loading="loading">{{ t('common.refresh') }}</el-button>
</div>
<div class="set-content">
<div v-for="member in filteredMembers" :key="member" class="set-item">
<span class="item-value">{{ member }}</span>
<el-button size="small" type="danger" text @click="removeMember(member)">Del</el-button>
<el-button size="small" type="danger" text @click="removeMember(member)">{{ t('editor.remove') }}</el-button>
</div>
<div v-if="filteredMembers.length === 0 && !loading" class="empty">Empty set</div>
<div v-if="filteredMembers.length === 0 && !loading" class="empty">{{ t('editor.empty') }}</div>
</div>
<el-dialog v-model="showAddDialog" title="Add Member" width="400px">
<el-form-item label="Member">
<el-dialog v-model="showAddDialog" :title="t('editor.addMember')" width="400px">
<el-form-item :label="t('editor.member')">
<el-input v-model="newMember" />
</el-form-item>
<template #footer>
<el-button @click="showAddDialog = false">Cancel</el-button>
<el-button type="primary" @click="addMember">Add</el-button>
<el-button @click="showAddDialog = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="addMember">{{ t('common.add') }}</el-button>
</template>
</el-dialog>
</div>
+9 -7
View File
@@ -1,9 +1,11 @@
<script setup lang="ts">
// src/renderer/src/components/SlowLogView.vue
// src/components/SlowLogView.vue
import { ref } from 'vue'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
const connStore = useConnectionStore()
const { t } = useI18n()
interface SlowLogEntry {
id: number
@@ -47,18 +49,18 @@ defineExpose({ refresh })
<template>
<div class="slow-log">
<div class="toolbar">
<span class="title">Slow Log</span>
<el-button size="small" @click="refresh" :loading="loading">Refresh</el-button>
<span class="title">{{ t('slowlog.title') }}</span>
<el-button size="small" @click="refresh" :loading="loading">{{ t('common.refresh') }}</el-button>
</div>
<div class="content">
<el-table :data="entries" size="small" max-height="400" empty-text="No slow log entries">
<el-table-column label="Time" width="180">
<el-table :data="entries" size="small" max-height="400" :empty-text="t('common.noData')">
<el-table-column :label="t('slowlog.time')" width="180">
<template #default="{ row }">{{ formatTime(row.timestamp) }}</template>
</el-table-column>
<el-table-column label="Duration" width="100">
<el-table-column :label="t('slowlog.duration')" width="100">
<template #default="{ row }">{{ formatDuration(row.duration) }}</template>
</el-table-column>
<el-table-column label="Command" min-width="300">
<el-table-column :label="t('slowlog.command')" min-width="300">
<template #default="{ row }">
<span class="cmd-text">{{ row.command }}</span>
</template>
+6 -4
View File
@@ -1,9 +1,11 @@
<script setup lang="ts">
// src/renderer/src/components/StatusView.vue
// src/components/StatusView.vue
import { ref, onMounted } from 'vue'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
const connStore = useConnectionStore()
const { t } = useI18n()
const info = ref('')
const dbSize = ref(0)
@@ -55,16 +57,16 @@ defineExpose({ refresh })
<div class="status-view" v-if="connStore.activeConnection">
<div class="status-header">
<h2>{{ connStore.activeConnection.name }}</h2>
<el-button size="small" @click="refresh">Refresh</el-button>
<el-button size="small" @click="refresh">{{ t('common.refresh') }}</el-button>
</div>
<div class="info-grid">
<div class="info-card">
<div class="info-label">Database</div>
<div class="info-label">{{ t('status.database') }}</div>
<div class="info-value">{{ connStore.activeConnection.host }}:{{ connStore.activeConnection.port }}</div>
</div>
<div class="info-card">
<div class="info-label">Keys</div>
<div class="info-label">{{ t('status.keys') }}</div>
<div class="info-value">{{ dbSize.toLocaleString() }}</div>
</div>
</div>
+19 -17
View File
@@ -1,12 +1,14 @@
<script setup lang="ts">
// src/renderer/src/components/StreamEditor.vue
// src/components/StreamEditor.vue
import { ref, watch } from 'vue'
import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
const keyStore = useKeyStore()
const connStore = useConnectionStore()
const { t } = useI18n()
interface StreamEntry {
id: string
@@ -125,20 +127,20 @@ async function trimStream() {
<div class="editor-toolbar">
<span class="type-badge">STREAM</span>
<span class="item-count">{{ entries.length }} entries</span>
<el-button size="small" type="primary" @click="showAddDialog = true">Add</el-button>
<el-popconfirm title="Trim stream?">
<el-button size="small" type="primary" @click="showAddDialog = true">{{ t('common.add') }}</el-button>
<el-popconfirm :title="t('editor.trimConfirm')">
<template #reference>
<el-button size="small" :loading="trimming">Trim</el-button>
<el-button size="small" :loading="trimming">{{ t('editor.trim') }}</el-button>
</template>
</el-popconfirm>
<el-button size="small" @click="loadEntries" :loading="loading">Refresh</el-button>
<el-button size="small" @click="loadEntries" :loading="loading">{{ t('common.refresh') }}</el-button>
</div>
<div class="stream-content">
<div v-for="entry in entries" :key="entry.id" class="stream-entry">
<div class="entry-header">
<span class="entry-id">{{ entry.id }}</span>
<el-button size="small" type="danger" text @click="deleteEntry(entry)">Del</el-button>
<el-button size="small" type="danger" text @click="deleteEntry(entry)">{{ t('common.delete') }}</el-button>
</div>
<div class="entry-fields">
<div v-for="(val, key) in entry.fields" :key="key" class="field-row">
@@ -147,34 +149,34 @@ async function trimStream() {
</div>
</div>
</div>
<div v-if="entries.length === 0 && !loading" class="empty">Empty stream</div>
<div v-if="entries.length === 0 && !loading" class="empty">{{ t('editor.empty') }}</div>
</div>
<el-dialog v-model="showAddDialog" title="Add Stream Entry" width="500px">
<el-dialog v-model="showAddDialog" :title="t('editor.addEntry')" width="500px">
<el-form label-position="top">
<el-form-item label="ID (use * for auto)">
<el-form-item :label="t('editor.entryId')">
<el-input v-model="newEntryId" />
</el-form-item>
<el-form-item label="Fields">
<el-form-item :label="t('editor.fields')">
<div v-for="(_, index) in newFields" :key="index" class="field-input-row">
<el-input v-model="newFields[index].key" placeholder="Field" size="small" />
<el-input v-model="newFields[index].value" placeholder="Value" size="small" />
<el-input v-model="newFields[index].key" :placeholder="t('editor.field')" size="small" />
<el-input v-model="newFields[index].value" :placeholder="t('editor.value')" size="small" />
<el-button size="small" type="danger" text @click="removeField(index)"
:disabled="newFields.length === 1">
<el-icon><Delete /></el-icon>
</el-button>
</div>
<el-button size="small" type="primary" text @click="addField">+ Add field</el-button>
<el-button size="small" type="primary" text @click="addField">+ {{ t('editor.addField') }}</el-button>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">Cancel</el-button>
<el-button type="primary" @click="addEntry">Add</el-button>
<el-button @click="showAddDialog = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="addEntry">{{ t('common.add') }}</el-button>
</template>
</el-dialog>
<el-dialog v-model="trimming" title="Trim Stream" width="300px">
<el-form-item label="Max length">
<el-dialog v-model="trimming" :title="t('editor.trim')" width="300px">
<el-form-item :label="t('editor.maxLength')">
<el-input-number v-model="trimMaxLen" :min="1" />
</el-form-item>
</el-dialog>
+14 -12
View File
@@ -1,12 +1,14 @@
<script setup lang="ts">
// src/renderer/src/components/ZsetEditor.vue
// src/components/ZsetEditor.vue
import { ref, watch } from 'vue'
import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
const keyStore = useKeyStore()
const connStore = useConnectionStore()
const { t } = useI18n()
interface ZsetItem {
member: string
@@ -80,33 +82,33 @@ watch([items, filterText], () => {
<div class="zset-editor">
<div class="editor-toolbar">
<span class="type-badge">ZSET</span>
<span class="item-count">{{ items.length }} members</span>
<el-input v-model="filterText" placeholder="Filter..." size="small" clearable style="width: 150px" />
<el-button size="small" type="primary" @click="showAddDialog = true">Add</el-button>
<el-button size="small" @click="loadMembers" :loading="loading">Refresh</el-button>
<span class="item-count">{{ t('editor.members', { n: items.length }) }}</span>
<el-input v-model="filterText" :placeholder="t('editor.filter')" size="small" clearable style="width: 150px" />
<el-button size="small" type="primary" @click="showAddDialog = true">{{ t('common.add') }}</el-button>
<el-button size="small" @click="loadMembers" :loading="loading">{{ t('common.refresh') }}</el-button>
</div>
<div class="zset-content">
<div v-for="item in filteredItems" :key="item.member" class="zset-item">
<span class="item-score">{{ item.score }}</span>
<span class="item-member">{{ item.member }}</span>
<el-button size="small" type="danger" text @click="removeMember(item.member)">Del</el-button>
<el-button size="small" type="danger" text @click="removeMember(item.member)">{{ t('editor.remove') }}</el-button>
</div>
<div v-if="filteredItems.length === 0 && !loading" class="empty">Empty sorted set</div>
<div v-if="filteredItems.length === 0 && !loading" class="empty">{{ t('editor.empty') }}</div>
</div>
<el-dialog v-model="showAddDialog" title="Add Member" width="400px">
<el-dialog v-model="showAddDialog" :title="t('editor.addMember')" width="400px">
<el-form label-position="top">
<el-form-item label="Member">
<el-form-item :label="t('editor.member')">
<el-input v-model="newMember" />
</el-form-item>
<el-form-item label="Score">
<el-form-item :label="t('editor.score')">
<el-input-number v-model="newScore" :precision="2" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">Cancel</el-button>
<el-button type="primary" @click="addMember">Add</el-button>
<el-button @click="showAddDialog = false">{{ t('common.cancel') }}</el-button>
<el-button type="primary" @click="addMember">{{ t('common.add') }}</el-button>
</template>
</el-dialog>
</div>
+9
View File
@@ -61,6 +61,8 @@ export default {
batchConfirm: 'Delete {n} key(s)?',
batchDeleted: 'Deleted {n} key(s)',
selected: '{n} selected',
selectKey: 'Select a key to view details',
unsupported: 'Type "{type}" viewer not yet implemented',
},
types: {
string: 'STRING',
@@ -78,6 +80,7 @@ export default {
push: 'Push',
addField: 'Add Field',
addMember: 'Add Member',
addEntry: 'Add Entry',
filter: 'Filter...',
refresh: 'Refresh',
elements: '{n} elements',
@@ -86,9 +89,15 @@ export default {
field: 'Field',
value: 'Value',
score: 'Score',
member: 'Member',
remove: 'Remove',
removed: 'Removed',
added: 'Added',
trim: 'Trim',
trimConfirm: 'Trim stream?',
entryId: 'ID (use * for auto)',
fields: 'Fields',
maxLength: 'Max length',
},
cli: {
title: 'CLI',
+9
View File
@@ -61,6 +61,8 @@ export default {
batchConfirm: '删除 {n} 个键?',
batchDeleted: '已删除 {n} 个键',
selected: '已选 {n} 项',
selectKey: '选择一个键查看详情',
unsupported: '类型 "{type}" 查看器尚未实现',
},
types: {
string: '字符串',
@@ -78,6 +80,7 @@ export default {
push: '推入',
addField: '添加字段',
addMember: '添加成员',
addEntry: '添加条目',
filter: '过滤...',
refresh: '刷新',
elements: '{n} 个元素',
@@ -86,9 +89,15 @@ export default {
field: '字段',
value: '值',
score: '分数',
member: '成员',
remove: '移除',
removed: '已移除',
added: '已添加',
trim: '裁剪',
trimConfirm: '裁剪流?',
entryId: 'ID (使用 * 自动生成)',
fields: '字段',
maxLength: '最大长度',
},
cli: {
title: 'CLI',
+19 -1
View File
@@ -45,6 +45,13 @@ export const useConnectionStore = defineStore('connection', () => {
connections.value = await window.electronAPI.storage.getConnections()
}
async function decryptPassword(auth: string): Promise<string> {
if (auth.startsWith('enc:')) {
return await window.electronAPI.credential.decrypt(auth.slice(4))
}
return auth
}
async function saveConnection(conn: ConnectionConfig) {
if (conn.auth && !conn.auth.startsWith('enc:')) {
conn.auth = 'enc:' + await window.electronAPI.credential.encrypt(conn.auth)
@@ -99,6 +106,17 @@ export const useConnectionStore = defineStore('connection', () => {
}
}
async function getInfo(): Promise<string> {
if (!activeId.value) return ''
try {
const info = await window.electronAPI.redis.getInfo(activeId.value)
serverInfo.value = info
return info
} catch {
return ''
}
}
function startHealthCheck(connId: string) {
stopHealthCheck()
healthOk.value = true
@@ -176,7 +194,7 @@ export const useConnectionStore = defineStore('connection', () => {
return {
connections, activeId, connecting, error, serverInfo, healthOk,
activeConnection, isConnected,
loadConnections, saveConnection, deleteConnection, reorderConnections,
loadConnections, decryptPassword, getInfo, saveConnection, deleteConnection, reorderConnections,
connect, disconnect, testConnection, exportConnections, importConnections,
}
})