feat: TLS certificate file support

- NewConnectionDialog: TLS cert file selectors (CA/Cert/Key) + rejectUnauthorized checkbox
- connection.ts: read cert files and pass to ioredis tls options
- Add tlsRejectUnauthorized to all ConnectionConfig types
- i18n keys for tlsCaPath/tlsCertPath/tlsKeyPath/tlsRejectUnauthorized
This commit is contained in:
2026-07-05 16:35:38 +08:00
parent 6e6f170403
commit 1686af1a79
8 changed files with 102 additions and 8 deletions
+64 -4
View File
@@ -18,6 +18,10 @@ const form = reactive({
auth: '',
username: '',
tls: false,
tlsCaPath: '',
tlsCertPath: '',
tlsKeyPath: '',
tlsRejectUnauthorized: false,
separator: ':',
sshEnabled: false,
sshHost: '',
@@ -44,6 +48,9 @@ watch(() => props.visible, async (v) => {
auth: decryptedAuth,
username: props.connection.username,
tls: props.connection.tls,
tlsCaPath: props.connection.tlsCaPath || '',
tlsCertPath: props.connection.tlsCertPath || '',
tlsKeyPath: props.connection.tlsKeyPath || '',
separator: props.connection.separator,
sshEnabled: props.connection.sshEnabled || false,
sshHost: props.connection.sshHost || '',
@@ -57,7 +64,8 @@ watch(() => props.visible, async (v) => {
isEdit.value = false
Object.assign(form, {
name: '', host: '127.0.0.1', port: 6379,
auth: '', username: '', tls: false, separator: ':',
auth: '', username: '', tls: false, tlsCaPath: '', tlsCertPath: '', tlsKeyPath: '',
separator: ':',
sshEnabled: false, sshHost: '', sshPort: 22,
sshUsername: '', sshPassword: '', sshPrivateKeyPath: '', sshPassphrase: '',
})
@@ -79,6 +87,30 @@ async function selectFile() {
}
}
async function selectTlsCaFile() {
const result = await window.electronAPI.dialog.openFile({
title: t('connection.tlsCaPath'),
filters: [{ name: 'Certificate Files', extensions: ['pem', 'crt', 'cer', 'key'] }],
})
if (result) { form.tlsCaPath = result.path }
}
async function selectTlsCertFile() {
const result = await window.electronAPI.dialog.openFile({
title: t('connection.tlsCertPath'),
filters: [{ name: 'Certificate Files', extensions: ['pem', 'crt', 'cer'] }],
})
if (result) { form.tlsCertPath = result.path }
}
async function selectTlsKeyFile() {
const result = await window.electronAPI.dialog.openFile({
title: t('connection.tlsKeyPath'),
filters: [{ name: 'Key Files', extensions: ['pem', 'key'] }],
})
if (result) { form.tlsKeyPath = result.path }
}
async function handleTest() {
if (!form.host.trim()) {
ElMessage.warning('Host is required')
@@ -92,6 +124,10 @@ async function handleTest() {
auth: form.auth || undefined,
username: form.username || undefined,
tls: form.tls,
tlsCaPath: form.tlsCaPath,
tlsCertPath: form.tlsCertPath,
tlsKeyPath: form.tlsKeyPath,
tlsRejectUnauthorized: form.tlsRejectUnauthorized,
sshEnabled: form.sshEnabled,
sshHost: form.sshHost.trim(),
sshPort: Number(form.sshPort) || 22,
@@ -125,9 +161,10 @@ async function handleSave() {
auth: form.auth,
username: form.username.trim(),
tls: form.tls,
tlsCaPath: '',
tlsCertPath: '',
tlsKeyPath: '',
tlsCaPath: form.tlsCaPath,
tlsCertPath: form.tlsCertPath,
tlsKeyPath: form.tlsKeyPath,
tlsRejectUnauthorized: form.tlsRejectUnauthorized,
sshEnabled: form.sshEnabled,
sshHost: form.sshHost.trim(),
sshPort: Number(form.sshPort) || 22,
@@ -188,6 +225,29 @@ async function handleSave() {
<el-form-item>
<el-checkbox v-model="form.tls">{{ t('connection.tls') }}</el-checkbox>
</el-form-item>
<template v-if="form.tls">
<el-form-item :label="t('connection.tlsCaPath')">
<div style="display:flex;gap:8px;">
<el-input v-model="form.tlsCaPath" placeholder="/path/to/ca.pem" />
<el-button @click="selectTlsCaFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('connection.tlsCertPath')">
<div style="display:flex;gap:8px;">
<el-input v-model="form.tlsCertPath" placeholder="/path/to/client.pem" />
<el-button @click="selectTlsCertFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="t('connection.tlsKeyPath')">
<div style="display:flex;gap:8px;">
<el-input v-model="form.tlsKeyPath" placeholder="/path/to/client-key.pem" />
<el-button @click="selectTlsKeyFile">{{ t('connection.selectFile') }}</el-button>
</div>
</el-form-item>
<el-form-item>
<el-checkbox v-model="form.tlsRejectUnauthorized">{{ t('connection.tlsRejectUnauthorized') }}</el-checkbox>
</el-form-item>
</template>
<!-- SSH Tunnel -->
<el-divider />
+4
View File
@@ -30,6 +30,10 @@ export default {
password: 'Password',
username: 'Username',
tls: 'Enable TLS',
tlsCaPath: 'CA Certificate',
tlsCertPath: 'Client Certificate',
tlsKeyPath: 'Client Private Key',
tlsRejectUnauthorized: 'Verify Server Certificate',
ssh: 'SSH Tunnel',
sshHost: 'SSH Host',
sshPort: 'SSH Port',
+4
View File
@@ -30,6 +30,10 @@ export default {
password: '密码',
username: '用户名',
tls: '启用 TLS',
tlsCaPath: 'CA 证书',
tlsCertPath: '客户端证书',
tlsKeyPath: '客户端私钥',
tlsRejectUnauthorized: '验证服务器证书',
ssh: 'SSH 隧道',
sshHost: 'SSH 主机',
sshPort: 'SSH 端口',
+10
View File
@@ -13,6 +13,7 @@ export interface ConnectionConfig {
tlsCaPath: string
tlsCertPath: string
tlsKeyPath: string
tlsRejectUnauthorized: boolean
sshEnabled: boolean
sshHost: string
sshPort: number
@@ -83,6 +84,10 @@ export const useConnectionStore = defineStore('connection', () => {
password: auth || undefined,
username: conn.username || undefined,
tls: conn.tls,
tlsCaPath: conn.tlsCaPath,
tlsCertPath: conn.tlsCertPath,
tlsKeyPath: conn.tlsKeyPath,
tlsRejectUnauthorized: conn.tlsRejectUnauthorized,
sshEnabled: conn.sshEnabled,
sshHost: conn.sshHost,
sshPort: conn.sshPort,
@@ -151,6 +156,7 @@ export const useConnectionStore = defineStore('connection', () => {
async function testConnection(opts: {
host: string; port: number; auth?: string; username?: string; tls?: boolean
tlsCaPath?: string; tlsCertPath?: string; tlsKeyPath?: string
sshEnabled?: boolean; sshHost?: string; sshPort?: number; sshUsername?: string
sshPassword?: string; sshPrivateKeyPath?: string; sshPassphrase?: string
}): Promise<{ success: boolean; error?: string }> {
@@ -165,6 +171,10 @@ export const useConnectionStore = defineStore('connection', () => {
password: opts.auth || undefined,
username: opts.username || undefined,
tls: opts.tls,
tlsCaPath: opts.tlsCaPath,
tlsCertPath: opts.tlsCertPath,
tlsKeyPath: opts.tlsKeyPath,
tlsRejectUnauthorized: (opts as any).tlsRejectUnauthorized,
sshEnabled: opts.sshEnabled,
sshHost: opts.sshHost,
sshPort: opts.sshPort,