feat: Pro 买断门禁、SFTP 精简与批量多机改造
新增 entitlement/license IPC 与 Waffo 一次性结账服务骨架,挂载 AI/转发/代理/串口/广播/批量导入导出门禁;批量执行改为结果矩阵并复用连接池,SFTP 工具栏图标化并去掉已连接条,同时完善跨平台本地终端与窗口配置。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
101d2164b4
commit
fea6d8db04
72 files changed
+3307
-396
No files matched your search
+13
-18
@@ -7,7 +7,7 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useSessionStore } from '../stores'
|
||||
import { useTabStore, type ConnectionInstance } from '../stores/tabStore'
|
||||
import { useConnectionPool, getSessionConfig } from '../stores/connection'
|
||||
import { useConnectionPool, getConnectionStore, getSessionConfig } from '../stores/connection'
|
||||
import { createLogger } from '../utils/logger'
|
||||
import type { FileItem, TransferTask, SortField, SortOrder } from '../../shared/types/sftp.types'
|
||||
import {
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
remotePath as toRemotePath,
|
||||
uploadRemoteFile,
|
||||
} from '../platform/sftp'
|
||||
import { joinLocalPath } from '../utils/localPath'
|
||||
|
||||
const logger = createLogger('useSFTP')
|
||||
|
||||
@@ -155,11 +156,6 @@ function fileNameFromPath(filePath: string): string {
|
||||
return filePath.split(/[\\/]/).filter(Boolean).pop() || filePath
|
||||
}
|
||||
|
||||
function joinLocalPath(directory: string, fileName: string): string {
|
||||
const separator = directory.includes('\\') && !directory.includes('/') ? '\\' : '/'
|
||||
return `${directory.replace(/[\\/]$/, '')}${separator}${fileName}`
|
||||
}
|
||||
|
||||
export function useSFTP({ tabId }: UseSFTPOptions): UseSFTPReturn {
|
||||
const { sessions } = useSessionStore()
|
||||
const { updateTabConnectionInstance, updateTab } = useTabStore()
|
||||
@@ -169,7 +165,7 @@ export function useSFTP({ tabId }: UseSFTPOptions): UseSFTPReturn {
|
||||
const [localPath, setLocalPath] = useState('')
|
||||
const [localFiles, setLocalFiles] = useState<FileItem[]>([])
|
||||
const [localSelected, setLocalSelected] = useState<Set<string>>(new Set())
|
||||
const [localHistory, setLocalHistory] = useState<string[]>(['/'])
|
||||
const [localHistory, setLocalHistory] = useState<string[]>([])
|
||||
const [localHistoryIndex, setLocalHistoryIndex] = useState(0)
|
||||
|
||||
// Remote state
|
||||
@@ -246,7 +242,7 @@ export function useSFTP({ tabId }: UseSFTPOptions): UseSFTPReturn {
|
||||
const type = localEntryType(f)
|
||||
return {
|
||||
name: f.name,
|
||||
path: f.path || `${path}/${f.name}`.replace(/\/+/g, '/'),
|
||||
path: f.path || joinLocalPath(path, f.name),
|
||||
type,
|
||||
size: f.size || 0,
|
||||
modified: f.modified ?? f.mtime ?? Date.now() / 1000,
|
||||
@@ -467,20 +463,19 @@ export function useSFTP({ tabId }: UseSFTPOptions): UseSFTPReturn {
|
||||
|
||||
// Disconnect
|
||||
const disconnect = useCallback(async () => {
|
||||
// Close SFTP session via SSH disconnect (SFTP is cleaned up when connection closes)
|
||||
if (connectionId && window.opsxApi) {
|
||||
if (connectionId) {
|
||||
try {
|
||||
await window.opsxApi.sshDisconnect(connectionId)
|
||||
logger.info('SFTP connection closed', { connectionId, sftpId })
|
||||
await getConnectionStore().releaseConnection(connectionId, tabId)
|
||||
logger.info('SFTP connection released', { connectionId, sftpId })
|
||||
} catch (err) {
|
||||
logger.error('Error closing SFTP connection', err)
|
||||
logger.error('Error releasing SFTP connection', err)
|
||||
}
|
||||
}
|
||||
setIsConnected(false)
|
||||
setSftpId(null)
|
||||
setConnectionId(null)
|
||||
setRemoteFiles([])
|
||||
}, [connectionId, sftpId])
|
||||
}, [connectionId, sftpId, tabId])
|
||||
|
||||
// Upload files (real implementation via IPC)
|
||||
const uploadFiles = useCallback(
|
||||
@@ -845,17 +840,17 @@ export function useSFTP({ tabId }: UseSFTPOptions): UseSFTPReturn {
|
||||
[sftpId, remotePath, loadRemoteDirectory]
|
||||
)
|
||||
|
||||
// Cleanup on unmount — use refs to avoid re-running when connectionId changes
|
||||
// Cleanup on unmount — release pool ref instead of hard-disconnecting shared sessions
|
||||
const connectionIdRef = useRef(connectionId)
|
||||
connectionIdRef.current = connectionId
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
const cid = connectionIdRef.current
|
||||
if (cid && window.opsxApi) {
|
||||
window.opsxApi.sshDisconnect(cid).catch(() => {})
|
||||
if (cid) {
|
||||
void getConnectionStore().releaseConnection(cid, tabId)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [tabId])
|
||||
|
||||
return {
|
||||
sessions,
|
||||
|
||||
Reference in new issue
Block a user