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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user