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
@@ -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)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user