@electerm/electerm-react 3.8.15 → 3.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/common/pre.js +0 -1
- package/client/components/bookmark-form/bookmark-from-history-modal.jsx +0 -1
- package/client/components/bookmark-form/config/rdp.js +4 -2
- package/client/components/icons/heartbeat.jsx +23 -0
- package/client/components/main/main.jsx +3 -1
- package/client/components/session/session.jsx +3 -3
- package/client/components/setting-sync/setting-sync-form.jsx +9 -1
- package/client/components/setting-sync/setting-sync.jsx +2 -1
- package/client/components/sftp/list-table-ui.jsx +33 -54
- package/client/components/sftp/paged-list.jsx +44 -44
- package/client/components/sftp/sftp-entry.jsx +5 -4
- package/client/components/sidebar/info-modal.jsx +8 -2
- package/client/components/tabs/tab.jsx +38 -19
- package/client/components/tabs/tabs.styl +8 -17
- package/client/components/terminal/attach-addon-custom.js +7 -3
- package/client/components/terminal/reconnect-overlay.jsx +2 -15
- package/client/components/terminal/terminal-command-dropdown.jsx +1 -1
- package/client/components/terminal/terminal-error-handle.jsx +43 -0
- package/client/components/terminal/terminal.jsx +40 -38
- package/client/components/terminal/terminal.styl +12 -7
- package/client/components/terminal/unix-timestamp-tooltip.jsx +85 -0
- package/client/components/text-editor/edit-with-custom-editor.jsx +22 -3
- package/client/components/text-editor/text-editor.jsx +21 -0
- package/client/components/tree-list/bookmark-toolbar.jsx +5 -5
- package/client/components/tree-list/tree-list-item.jsx +6 -12
- package/client/components/tree-list/tree-list-row.jsx +5 -0
- package/client/components/tree-list/tree-list-rows.js +3 -1
- package/client/components/widgets/widget-control.jsx +1 -0
- package/client/store/common.js +1 -1
- package/client/store/load-data.js +2 -2
- package/client/store/mcp-handler.js +83 -10
- package/client/store/sync.js +3 -2
- package/client/store/tab.js +34 -0
- package/package.json +1 -1
- package/client/components/terminal/socket-close-warning.jsx +0 -94
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import uid from '../common/uid'
|
|
7
7
|
import { settingMap } from '../common/constants'
|
|
8
|
-
import { refs } from '../components/common/ref'
|
|
8
|
+
import { refs, refsTabs } from '../components/common/ref'
|
|
9
9
|
import deepCopy from 'json-deep-copy'
|
|
10
10
|
import {
|
|
11
11
|
getLocalFileInfo,
|
|
@@ -107,6 +107,9 @@ export default Store => {
|
|
|
107
107
|
case 'get_terminal_output':
|
|
108
108
|
result = store.mcpGetTerminalOutput(args)
|
|
109
109
|
break
|
|
110
|
+
case 'wait_for_terminal_idle':
|
|
111
|
+
result = await store.mcpWaitForTerminalIdle(args)
|
|
112
|
+
break
|
|
110
113
|
|
|
111
114
|
// SFTP operations
|
|
112
115
|
case 'sftp_list':
|
|
@@ -331,15 +334,18 @@ export default Store => {
|
|
|
331
334
|
|
|
332
335
|
Store.prototype.mcpListTabs = function () {
|
|
333
336
|
const { store } = window
|
|
334
|
-
return store.tabs.map(t =>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
337
|
+
return store.tabs.map(t => {
|
|
338
|
+
return {
|
|
339
|
+
id: t.id,
|
|
340
|
+
title: t.title,
|
|
341
|
+
host: t.host,
|
|
342
|
+
type: t.type || 'local',
|
|
343
|
+
status: t.status,
|
|
344
|
+
isTransporting: t.isTransporting,
|
|
345
|
+
onData: refsTabs.get('tab-' + t.id)?.state.terminalOnData,
|
|
346
|
+
batch: t.batch
|
|
347
|
+
}
|
|
348
|
+
})
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
Store.prototype.mcpGetActiveTab = function () {
|
|
@@ -527,6 +533,73 @@ export default Store => {
|
|
|
527
533
|
}
|
|
528
534
|
}
|
|
529
535
|
|
|
536
|
+
Store.prototype.mcpWaitForTerminalIdle = async function (args) {
|
|
537
|
+
const { store } = window
|
|
538
|
+
const tabId = args.tabId || store.activeTabId
|
|
539
|
+
const timeout = Math.min(args.timeout || 30000, 120000)
|
|
540
|
+
const pollInterval = 500
|
|
541
|
+
const minWait = args.minWait !== undefined ? args.minWait : 1000
|
|
542
|
+
const lineCountToFetch = args.lines || 50
|
|
543
|
+
|
|
544
|
+
if (!tabId) {
|
|
545
|
+
throw new Error('No active terminal')
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const start = Date.now()
|
|
549
|
+
|
|
550
|
+
// Brief initial wait so the command has time to start producing output
|
|
551
|
+
if (minWait > 0) {
|
|
552
|
+
await new Promise(resolve => setTimeout(resolve, minWait))
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const collectOutput = () => {
|
|
556
|
+
const term = refs.get('term-' + tabId)
|
|
557
|
+
if (!term || !term.term) return { output: '', lineCount: 0 }
|
|
558
|
+
const buffer = term.term.buffer.active
|
|
559
|
+
if (!buffer) return { output: '', lineCount: 0 }
|
|
560
|
+
const cursorY = buffer.cursorY || 0
|
|
561
|
+
const baseY = buffer.baseY || 0
|
|
562
|
+
const totalLines = buffer.length || 0
|
|
563
|
+
const actualContentEnd = baseY + cursorY + 1
|
|
564
|
+
const startLine = Math.max(0, actualContentEnd - lineCountToFetch)
|
|
565
|
+
const endLine = Math.min(totalLines, actualContentEnd)
|
|
566
|
+
const lines = []
|
|
567
|
+
for (let i = startLine; i < endLine; i++) {
|
|
568
|
+
const line = buffer.getLine(i)
|
|
569
|
+
if (line) lines.push(line.translateToString(true))
|
|
570
|
+
}
|
|
571
|
+
return { output: lines.join('\n'), lineCount: lines.length }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Poll until onData becomes false (4s idle debounce in tab.jsx)
|
|
575
|
+
while (Date.now() - start < timeout) {
|
|
576
|
+
const tabRef = refsTabs.get('tab-' + tabId)
|
|
577
|
+
const onData = tabRef?.state.terminalOnData
|
|
578
|
+
if (!onData) {
|
|
579
|
+
const { output, lineCount } = collectOutput()
|
|
580
|
+
return {
|
|
581
|
+
tabId,
|
|
582
|
+
elapsed: Date.now() - start,
|
|
583
|
+
timedOut: false,
|
|
584
|
+
output,
|
|
585
|
+
lineCount
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval))
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Timeout reached — return whatever is currently in the buffer
|
|
592
|
+
const { output, lineCount } = collectOutput()
|
|
593
|
+
return {
|
|
594
|
+
tabId,
|
|
595
|
+
elapsed: Date.now() - start,
|
|
596
|
+
timedOut: true,
|
|
597
|
+
message: `Terminal still active after ${timeout}ms`,
|
|
598
|
+
output,
|
|
599
|
+
lineCount
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
530
603
|
// ==================== Settings APIs ====================
|
|
531
604
|
|
|
532
605
|
Store.prototype.mcpGetSettings = function () {
|
package/client/store/sync.js
CHANGED
|
@@ -68,11 +68,12 @@ export default (Store) => {
|
|
|
68
68
|
).join('####')
|
|
69
69
|
}
|
|
70
70
|
if (type === syncTypes.webdav) {
|
|
71
|
-
// WebDAV token format: serverUrl####username####password
|
|
71
|
+
// WebDAV token format: serverUrl####username####password####skipVerify
|
|
72
72
|
const serverUrl = get(window.store.config, 'syncSetting.webdavServerUrl')
|
|
73
73
|
const username = get(window.store.config, 'syncSetting.webdavUsername')
|
|
74
74
|
const password = get(window.store.config, 'syncSetting.webdavPassword')
|
|
75
|
-
|
|
75
|
+
const skipVerify = get(window.store.config, 'syncSetting.webdavSkipVerify') || false
|
|
76
|
+
return [serverUrl, username, password, skipVerify].join('####')
|
|
76
77
|
}
|
|
77
78
|
return get(window.store.config, 'syncSetting.' + type + 'AccessToken')
|
|
78
79
|
}
|
package/client/store/tab.js
CHANGED
|
@@ -362,6 +362,26 @@ export default Store => {
|
|
|
362
362
|
store.updateHistory(newTab)
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
// Dangerous props that should not be accepted from IPC
|
|
366
|
+
const dangerousTabProps = [
|
|
367
|
+
'execLinux',
|
|
368
|
+
'execMac',
|
|
369
|
+
'execWindows',
|
|
370
|
+
'execWindowsArgs',
|
|
371
|
+
'execMacArgs',
|
|
372
|
+
'execLinuxArgs',
|
|
373
|
+
'setEnv',
|
|
374
|
+
'runScripts',
|
|
375
|
+
'interactiveValues'
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
Store.prototype.ipcOpenTab = function (parsed) {
|
|
379
|
+
const safeTab = Object.fromEntries(
|
|
380
|
+
Object.entries(parsed).filter(([key]) => !dangerousTabProps.includes(key))
|
|
381
|
+
)
|
|
382
|
+
return window.store.addTab(safeTab)
|
|
383
|
+
}
|
|
384
|
+
|
|
365
385
|
Store.prototype.clickNextTab = debounce(function () {
|
|
366
386
|
window.store.clickBioTab(1)
|
|
367
387
|
}, 100)
|
|
@@ -602,6 +622,20 @@ export default Store => {
|
|
|
602
622
|
}
|
|
603
623
|
}
|
|
604
624
|
|
|
625
|
+
Store.prototype.notifyTabPasswordPrompt = function (tabId) {
|
|
626
|
+
const tab = refsTabs.get('tab-' + tabId)
|
|
627
|
+
if (tab) {
|
|
628
|
+
tab.notifyPasswordPrompt()
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
Store.prototype.clearTabPasswordPrompt = function (tabId) {
|
|
633
|
+
const tab = refsTabs.get('tab-' + tabId)
|
|
634
|
+
if (tab) {
|
|
635
|
+
tab.clearPasswordPrompt()
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
605
639
|
Store.prototype.remoteList = function (tabId) {
|
|
606
640
|
const sftp = refs.get('sftp-' + tabId)
|
|
607
641
|
if (sftp) {
|
package/package.json
CHANGED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react'
|
|
2
|
-
import { Button } from 'antd'
|
|
3
|
-
import { ReloadOutlined } from '@ant-design/icons'
|
|
4
|
-
import { notification } from '../common/notification'
|
|
5
|
-
|
|
6
|
-
const e = window.translate
|
|
7
|
-
const COUNTDOWN_SECONDS = 3
|
|
8
|
-
|
|
9
|
-
export function showSocketCloseWarning ({
|
|
10
|
-
tabId,
|
|
11
|
-
tab,
|
|
12
|
-
autoReconnect,
|
|
13
|
-
delTab,
|
|
14
|
-
reloadTab
|
|
15
|
-
}) {
|
|
16
|
-
const key = `open${Date.now()}`
|
|
17
|
-
|
|
18
|
-
function closeNotification () {
|
|
19
|
-
notification.destroy(key)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const descriptionNode = (
|
|
23
|
-
<SocketCloseDescription
|
|
24
|
-
autoReconnect={autoReconnect}
|
|
25
|
-
onClose={() => {
|
|
26
|
-
closeNotification()
|
|
27
|
-
delTab(tabId)
|
|
28
|
-
}}
|
|
29
|
-
onReload={() => {
|
|
30
|
-
closeNotification()
|
|
31
|
-
reloadTab({ ...tab, autoReConnect: (tab.autoReConnect || 0) + 1 })
|
|
32
|
-
}}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
notification.warning({
|
|
37
|
-
key,
|
|
38
|
-
message: e('socketCloseTip'),
|
|
39
|
-
duration: autoReconnect ? COUNTDOWN_SECONDS + 2 : 30,
|
|
40
|
-
description: descriptionNode
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
return { key }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function SocketCloseDescription ({ autoReconnect, onClose, onReload }) {
|
|
47
|
-
const [countdown, setCountdown] = useState(COUNTDOWN_SECONDS)
|
|
48
|
-
const timerRef = useRef(null)
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (!autoReconnect) {
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
timerRef.current = setInterval(() => {
|
|
55
|
-
setCountdown(prev => {
|
|
56
|
-
if (prev <= 1) {
|
|
57
|
-
clearInterval(timerRef.current)
|
|
58
|
-
onReload()
|
|
59
|
-
return 0
|
|
60
|
-
}
|
|
61
|
-
return prev - 1
|
|
62
|
-
})
|
|
63
|
-
}, 1000)
|
|
64
|
-
|
|
65
|
-
return () => {
|
|
66
|
-
if (timerRef.current) {
|
|
67
|
-
clearInterval(timerRef.current)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}, [autoReconnect])
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<div className='pd2y'>
|
|
74
|
-
{autoReconnect && (
|
|
75
|
-
<div className='pd1b'>
|
|
76
|
-
{e('autoReconnectTerminal')}: {countdown}s
|
|
77
|
-
</div>
|
|
78
|
-
)}
|
|
79
|
-
<Button
|
|
80
|
-
className='mg1r'
|
|
81
|
-
type='primary'
|
|
82
|
-
onClick={onClose}
|
|
83
|
-
>
|
|
84
|
-
{e('close')}
|
|
85
|
-
</Button>
|
|
86
|
-
<Button
|
|
87
|
-
icon={<ReloadOutlined />}
|
|
88
|
-
onClick={onReload}
|
|
89
|
-
>
|
|
90
|
-
{e('reload')}
|
|
91
|
-
</Button>
|
|
92
|
-
</div>
|
|
93
|
-
)
|
|
94
|
-
}
|