feat: Pro 买断门禁、SFTP 精简与批量多机改造

新增 entitlement/license IPC 与 Waffo 一次性结账服务骨架,挂载 AI/转发/代理/串口/广播/批量导入导出门禁;批量执行改为结果矩阵并复用连接池,SFTP 工具栏图标化并去掉已连接条,同时完善跨平台本地终端与窗口配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-07 11:38:30 +08:00
1 parent 101d2164b4
commit fea6d8db04
72 files changed
+3307 -396

No files matched your search

+40 -21
View File
@@ -5,13 +5,13 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import { Plus, Zap, Settings, ListChecks, Terminal, FolderSync, X, TerminalSquare, PanelLeftClose } from 'lucide-react'
import { useSessionStore, Session } from '../../stores'
import { useSessionStore, Session, useEntitlementStore } from '../../stores'
import { useSettingsStore } from '../../stores/settingsStore'
import { useTabStore } from '../../stores/tabStore'
import { useTerminalBroadcastStore } from '../../stores/terminalBroadcastStore'
import { SessionTree } from './sidebar/SessionTree'
import { SearchBox } from './sidebar/SearchBox'
import { useConfirmDialog } from '../common/ConfirmDialog'
import BatchCommandDialog from '../sessions/BatchCommandDialog'
import appIconUrl from '../../assets/opsxshell-app-icon.png'
const MIN_SIDEBAR_WIDTH = 180
@@ -39,8 +39,9 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession, o
const [sessionsLoading, setSessionsLoading] = useState(true)
const [batchMode, setBatchMode] = useState(false)
const [batchSelection, setBatchSelection] = useState<Set<string>>(new Set())
const [batchCommandSessions, setBatchCommandSessions] = useState<Session[]>([])
const [batchCommandOpen, setBatchCommandOpen] = useState(false)
const requirePro = useEntitlementStore((s) => s.requirePro)
const setBroadcastSelectedSessionIds = useTerminalBroadcastStore((s) => s.setSelectedSessionIds)
const setBroadcastScope = useTerminalBroadcastStore((s) => s.setScope)
const contextMenuRef = useRef<HTMLDivElement>(null)
const sidebarRef = useRef<HTMLDivElement>(null)
@@ -174,7 +175,14 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession, o
const finishBatchMode = useCallback(() => {
setBatchMode(false)
setBatchSelection(new Set())
}, [])
setBroadcastSelectedSessionIds([])
}, [setBroadcastSelectedSessionIds])
useEffect(() => {
if (!batchMode) return
setBroadcastSelectedSessionIds(Array.from(batchSelection))
setBroadcastScope('selected-sessions')
}, [batchMode, batchSelection, setBroadcastScope, setBroadcastSelectedSessionIds])
const handleBatchTerminal = useCallback(() => {
for (const session of selectedSessions) {
@@ -185,21 +193,30 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession, o
connectOnMount: true,
})
}
finishBatchMode()
}, [addTab, finishBatchMode, selectedSessions])
// Keep selection for sync-input / further batch actions
}, [addTab, selectedSessions])
const handleBatchSftp = useCallback(() => {
for (const session of selectedSessions) {
addTab({ title: `SFTP: ${session.name}`, type: 'sftp', sessionId: session.id })
}
finishBatchMode()
}, [addTab, finishBatchMode, selectedSessions])
}, [addTab, selectedSessions])
const handleBatchCommand = useCallback(() => {
setBatchCommandSessions(selectedSessions)
setBatchCommandOpen(true)
finishBatchMode()
}, [finishBatchMode, selectedSessions])
if (!requirePro('batchExec')) return
addTab({
title: '批量执行',
type: 'batch',
batchSessionIds: selectedSessions.map((session) => session.id),
})
}, [addTab, requirePro, selectedSessions])
const handleBatchSyncInput = useCallback(() => {
if (!requirePro('broadcast')) return
setBroadcastSelectedSessionIds(selectedSessions.map((s) => s.id))
setBroadcastScope('selected-sessions')
useTerminalBroadcastStore.getState().setEnabled(true)
}, [requirePro, selectedSessions, setBroadcastScope, setBroadcastSelectedSessionIds])
const handleContextMenu = (e: React.MouseEvent, session: Session) => {
e.preventDefault()
@@ -365,13 +382,21 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession, o
className="col-span-2 flex h-9 items-center justify-center gap-1.5 rounded bg-accent-blue text-xs font-medium text-white disabled:cursor-not-allowed disabled:opacity-40"
>
<TerminalSquare className="h-3.5 w-3.5" />
批量命令
批量执行 · Pro
</button>
<button
type="button"
disabled={selectedSessions.length === 0}
onClick={handleBatchSyncInput}
className="col-span-2 flex h-9 items-center justify-center gap-1.5 rounded border border-accent-orange/40 bg-accent-orange/10 text-xs font-medium text-accent-orange disabled:cursor-not-allowed disabled:opacity-40"
>
同步输入 · Pro
</button>
<button
type="button"
disabled={selectedSessions.length === 0}
onClick={handleBatchTerminal}
className="flex h-9 items-center justify-center gap-1.5 rounded bg-accent-blue text-xs font-medium text-white disabled:cursor-not-allowed disabled:opacity-40"
className="flex h-9 items-center justify-center gap-1.5 rounded bg-dark-700 text-xs font-medium text-content-primary disabled:cursor-not-allowed disabled:opacity-40"
>
<Terminal className="h-3.5 w-3.5" />
打开终端
@@ -450,12 +475,6 @@ export default function Sidebar({ onNewSession, onQuickConnect, onEditSession, o
/>
</div>
{ConfirmDialogComponent}
<BatchCommandDialog
isOpen={batchCommandOpen}
sessions={sessions}
initialSelectedIds={batchCommandSessions.map((session) => session.id)}
onClose={() => setBatchCommandOpen(false)}
/>
</>
)
}