feat: multi-connection parallel support

- connection store: connectedIds array, per-connection health/serverInfo maps
- connect() no longer disconnects existing connections
- New switchTo(id), disconnectById(id), isConnectedById(id) methods
- Per-connection health check timers (Map-based)
- ConnectionCard: switch to connected, connect if not, disconnect if active
- MainArea: per-connection tab state (save/restore on switch)
- Connection switcher dropdown in tab bar showing all open connections
- Clean up tab state when connection disconnects
This commit is contained in:
2026-07-07 20:31:57 +08:00
parent 11a73494c0
commit 6a1f94cd48
4 changed files with 185 additions and 33 deletions
+58 -23
View File
@@ -36,16 +36,23 @@ export interface ConnectionConfig {
export const useConnectionStore = defineStore('connection', () => {
const connections = ref<ConnectionConfig[]>([])
const activeId = ref<string | null>(null)
const connectedIds = ref<string[]>([])
const connecting = ref(false)
const error = ref<string | null>(null)
const serverInfo = ref<string | null>(null)
const healthOk = ref(true)
let healthTimer: ReturnType<typeof setInterval> | null = null
const serverInfoMap = ref<Record<string, string>>({})
const healthMap = ref<Record<string, boolean>>({})
const healthTimers = new Map<string, ReturnType<typeof setInterval>>()
const activeConnection = computed(() =>
connections.value.find((c: ConnectionConfig) => c.id === activeId.value) ?? null
)
const isConnected = computed(() => activeId.value !== null)
const isConnected = computed(() => activeId.value !== null && connectedIds.value.includes(activeId.value))
const serverInfo = computed(() => activeId.value ? serverInfoMap.value[activeId.value] ?? null : null)
const healthOk = computed(() => activeId.value ? healthMap.value[activeId.value] ?? false : false)
function isConnectedById(id: string): boolean {
return connectedIds.value.includes(id)
}
async function loadConnections() {
connections.value = await window.electronAPI.storage.getConnections()
@@ -66,7 +73,7 @@ export const useConnectionStore = defineStore('connection', () => {
}
async function deleteConnection(id: string) {
if (activeId.value === id) await disconnect()
if (connectedIds.value.includes(id)) await disconnectById(id)
connections.value = await window.electronAPI.storage.deleteConnection(id)
}
@@ -75,6 +82,11 @@ export const useConnectionStore = defineStore('connection', () => {
}
async function connect(conn: ConnectionConfig) {
// If already connected, just switch
if (connectedIds.value.includes(conn.id)) {
activeId.value = conn.id
return
}
connecting.value = true
error.value = null
try {
@@ -107,8 +119,9 @@ export const useConnectionStore = defineStore('connection', () => {
sshPassphrase: conn.sshPassphrase,
})
if (result.success) {
connectedIds.value.push(conn.id)
activeId.value = conn.id
serverInfo.value = await window.electronAPI.redis.getInfo(conn.id)
serverInfoMap.value[conn.id] = await window.electronAPI.redis.getInfo(conn.id)
startHealthCheck(conn.id)
} else {
error.value = result.error || 'Connection failed'
@@ -120,12 +133,26 @@ export const useConnectionStore = defineStore('connection', () => {
}
}
function switchTo(id: string) {
if (connectedIds.value.includes(id)) {
activeId.value = id
}
}
async function disconnectById(id: string) {
stopHealthCheck(id)
await window.electronAPI.redis.disconnect(id)
connectedIds.value = connectedIds.value.filter(i => i !== id)
delete serverInfoMap.value[id]
delete healthMap.value[id]
if (activeId.value === id) {
activeId.value = connectedIds.value[0] ?? null
}
}
async function disconnect() {
stopHealthCheck()
if (activeId.value) {
await window.electronAPI.redis.disconnect(activeId.value)
activeId.value = null
serverInfo.value = null
await disconnectById(activeId.value)
}
}
@@ -133,7 +160,7 @@ export const useConnectionStore = defineStore('connection', () => {
if (!activeId.value) return ''
try {
const info = await window.electronAPI.redis.getInfo(activeId.value)
serverInfo.value = info
serverInfoMap.value[activeId.value] = info
return info
} catch {
return ''
@@ -141,26 +168,34 @@ export const useConnectionStore = defineStore('connection', () => {
}
function startHealthCheck(connId: string) {
stopHealthCheck()
healthOk.value = true
healthTimer = setInterval(async () => {
stopHealthCheck(connId)
healthMap.value[connId] = true
const timer = setInterval(async () => {
try {
const ok = await window.electronAPI.redis.isConnected(connId)
healthOk.value = ok
healthMap.value[connId] = ok
if (!ok && activeId.value === connId) {
// Connection lost
error.value = 'Connection lost'
}
} catch {
healthOk.value = false
healthMap.value[connId] = false
}
}, 5000)
healthTimers.set(connId, timer)
}
function stopHealthCheck() {
if (healthTimer) {
clearInterval(healthTimer)
healthTimer = null
function stopHealthCheck(connId?: string) {
if (connId) {
const timer = healthTimers.get(connId)
if (timer) {
clearInterval(timer)
healthTimers.delete(connId)
}
} else {
for (const [, timer] of healthTimers) {
clearInterval(timer)
}
healthTimers.clear()
}
}
@@ -238,9 +273,9 @@ export const useConnectionStore = defineStore('connection', () => {
}
return {
connections, activeId, connecting, error, serverInfo, healthOk,
connections, activeId, connectedIds, connecting, error, serverInfo, healthOk,
activeConnection, isConnected,
loadConnections, decryptPassword, getInfo, saveConnection, deleteConnection, reorderConnections,
connect, disconnect, testConnection, exportConnections, importConnections,
connect, disconnect, disconnectById, switchTo, isConnectedById, testConnection, exportConnections, importConnections,
}
})