feat(key-detail): live TTL countdown with expiry check and i18n

This commit is contained in:
2026-07-12 11:54:15 +08:00
parent 775b66054a
commit bf46cb160a
6 changed files with 50 additions and 6 deletions
+40 -6
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref, watch, onUnmounted } from 'vue'
import { useKeyStore } from '@renderer/stores/key' import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection' import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n' import { useI18n } from '@renderer/i18n'
@@ -34,13 +34,46 @@ const typeColor = computed(() => {
return colors[keyStore.selectedType || ''] || 'var(--text-muted)' return colors[keyStore.selectedType || ''] || 'var(--text-muted)'
}) })
const liveTTL = ref(keyStore.selectedTTL)
watch(() => keyStore.selectedTTL, (v) => {
liveTTL.value = v
})
let ttlTimer: ReturnType<typeof setInterval> | null = setInterval(async () => {
if (liveTTL.value > 0) {
liveTTL.value--
if (liveTTL.value === 0) {
const connId = connStore.activeId
const key = keyStore.selectedKey
if (!connId || !key) return
const exists = await window.electronAPI.redis.existsKey(connId, key)
// 用户可能已切换 key,跳过过期检查
if (keyStore.selectedKey !== key) return
if (!exists) {
keyStore.selectedType = 'none'
} else {
// key 仍存在(TTL 可能被其他客户端刷新),同步实际 TTL
const ttl = await window.electronAPI.redis.getKeyTTL(connId, key)
if (keyStore.selectedKey !== key) return
keyStore.selectedTTL = ttl
liveTTL.value = ttl
}
}
}
}, 1000)
onUnmounted(() => {
if (ttlTimer) clearInterval(ttlTimer)
})
const ttlDisplay = computed(() => { const ttlDisplay = computed(() => {
if (keyStore.selectedTTL < 0) return 'No expiry' if (liveTTL.value < 0) return t('key.noExpiry')
if (keyStore.selectedTTL === 0) return 'Expired' if (liveTTL.value === 0) return t('key.expired')
const sec = keyStore.selectedTTL const sec = liveTTL.value
if (sec < 60) return `${sec}s` if (sec < 60) return `${sec}s`
if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s` if (sec < 3600) return `${Math.floor(sec / 60)}m ${sec % 60}s`
return `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m` return `${Math.floor(sec / 3600)}h ${Math.floor((sec % 3600) / 60)}m ${sec % 60}s`
}) })
function openTtlDialog() { function openTtlDialog() {
@@ -53,6 +86,7 @@ async function saveTtl() {
try { try {
await window.electronAPI.redis.setTTL(connStore.activeId, keyStore.selectedKey, ttlValue.value) await window.electronAPI.redis.setTTL(connStore.activeId, keyStore.selectedKey, ttlValue.value)
keyStore.selectedTTL = ttlValue.value keyStore.selectedTTL = ttlValue.value
liveTTL.value = ttlValue.value
showTtlDialog.value = false showTtlDialog.value = false
ElMessage.success('TTL updated') ElMessage.success('TTL updated')
} catch (err: any) { } catch (err: any) {
@@ -246,7 +280,7 @@ async function refreshKey() {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
max-width: 300px; max-width: 500px;
} }
.copy-btn { .copy-btn {
+2
View File
@@ -105,6 +105,8 @@ export default {
ttlTitle: 'TTL festlegen', ttlTitle: 'TTL festlegen',
ttlHint: '-1 = kein Ablauf, 0 = sofort ablaufen', ttlHint: '-1 = kein Ablauf, 0 = sofort ablaufen',
ttlUpdated: 'TTL aktualisiert', ttlUpdated: 'TTL aktualisiert',
noExpiry: 'Kein Ablauf',
expired: 'Abgelaufen',
exists: 'Schlüssel existiert bereits', exists: 'Schlüssel existiert bereits',
batchDelete: 'Ausgewählte löschen', batchDelete: 'Ausgewählte löschen',
batchConfirm: '{n} Schlüssel löschen?', batchConfirm: '{n} Schlüssel löschen?',
+2
View File
@@ -105,6 +105,8 @@ export default {
ttlTitle: 'Set TTL', ttlTitle: 'Set TTL',
ttlHint: '-1 = no expiry, 0 = expire now', ttlHint: '-1 = no expiry, 0 = expire now',
ttlUpdated: 'TTL updated', ttlUpdated: 'TTL updated',
noExpiry: 'No expiry',
expired: 'Expired',
exists: 'Key already exists', exists: 'Key already exists',
batchDelete: 'Delete Selected', batchDelete: 'Delete Selected',
batchConfirm: 'Delete {n} key(s)?', batchConfirm: 'Delete {n} key(s)?',
+2
View File
@@ -105,6 +105,8 @@ export default {
ttlTitle: 'TTLを設定', ttlTitle: 'TTLを設定',
ttlHint: '-1 = 無期限, 0 = 即時期限切れ', ttlHint: '-1 = 無期限, 0 = 即時期限切れ',
ttlUpdated: 'TTLを更新しました', ttlUpdated: 'TTLを更新しました',
noExpiry: '無期限',
expired: '期限切れ',
exists: 'キーは既に存在します', exists: 'キーは既に存在します',
batchDelete: '選択を削除', batchDelete: '選択を削除',
batchConfirm: '{n} 個のキーを削除しますか?', batchConfirm: '{n} 個のキーを削除しますか?',
+2
View File
@@ -105,6 +105,8 @@ export default {
ttlTitle: 'TTL 설정', ttlTitle: 'TTL 설정',
ttlHint: '-1 = 만료 없음, 0 = 즉시 만료', ttlHint: '-1 = 만료 없음, 0 = 즉시 만료',
ttlUpdated: 'TTL이 업데이트되었습니다', ttlUpdated: 'TTL이 업데이트되었습니다',
noExpiry: '만료 없음',
expired: '만료됨',
exists: '키가 이미 존재합니다', exists: '키가 이미 존재합니다',
batchDelete: '선택 삭제', batchDelete: '선택 삭제',
batchConfirm: '{n}개 키를 삭제하시겠습니까?', batchConfirm: '{n}개 키를 삭제하시겠습니까?',
+2
View File
@@ -105,6 +105,8 @@ export default {
ttlTitle: '设置 TTL', ttlTitle: '设置 TTL',
ttlHint: '-1 = 永不过期, 0 = 立即过期', ttlHint: '-1 = 永不过期, 0 = 立即过期',
ttlUpdated: 'TTL 已更新', ttlUpdated: 'TTL 已更新',
noExpiry: '永不过期',
expired: '已过期',
exists: '键已存在', exists: '键已存在',
batchDelete: '删除选中', batchDelete: '删除选中',
batchConfirm: '删除 {n} 个键?', batchConfirm: '删除 {n} 个键?',