@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.
Files changed (35) hide show
  1. package/client/common/pre.js +0 -1
  2. package/client/components/bookmark-form/bookmark-from-history-modal.jsx +0 -1
  3. package/client/components/bookmark-form/config/rdp.js +4 -2
  4. package/client/components/icons/heartbeat.jsx +23 -0
  5. package/client/components/main/main.jsx +3 -1
  6. package/client/components/session/session.jsx +3 -3
  7. package/client/components/setting-sync/setting-sync-form.jsx +9 -1
  8. package/client/components/setting-sync/setting-sync.jsx +2 -1
  9. package/client/components/sftp/list-table-ui.jsx +33 -54
  10. package/client/components/sftp/paged-list.jsx +44 -44
  11. package/client/components/sftp/sftp-entry.jsx +5 -4
  12. package/client/components/sidebar/info-modal.jsx +8 -2
  13. package/client/components/tabs/tab.jsx +38 -19
  14. package/client/components/tabs/tabs.styl +8 -17
  15. package/client/components/terminal/attach-addon-custom.js +7 -3
  16. package/client/components/terminal/reconnect-overlay.jsx +2 -15
  17. package/client/components/terminal/terminal-command-dropdown.jsx +1 -1
  18. package/client/components/terminal/terminal-error-handle.jsx +43 -0
  19. package/client/components/terminal/terminal.jsx +40 -38
  20. package/client/components/terminal/terminal.styl +12 -7
  21. package/client/components/terminal/unix-timestamp-tooltip.jsx +85 -0
  22. package/client/components/text-editor/edit-with-custom-editor.jsx +22 -3
  23. package/client/components/text-editor/text-editor.jsx +21 -0
  24. package/client/components/tree-list/bookmark-toolbar.jsx +5 -5
  25. package/client/components/tree-list/tree-list-item.jsx +6 -12
  26. package/client/components/tree-list/tree-list-row.jsx +5 -0
  27. package/client/components/tree-list/tree-list-rows.js +3 -1
  28. package/client/components/widgets/widget-control.jsx +1 -0
  29. package/client/store/common.js +1 -1
  30. package/client/store/load-data.js +2 -2
  31. package/client/store/mcp-handler.js +83 -10
  32. package/client/store/sync.js +3 -2
  33. package/client/store/tab.js +34 -0
  34. package/package.json +1 -1
  35. 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
- id: t.id,
336
- title: t.title,
337
- host: t.host,
338
- type: t.type || 'local',
339
- status: t.status,
340
- isTransporting: t.isTransporting,
341
- batch: t.batch
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 () {
@@ -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
- return [serverUrl, username, password].join('####')
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
  }
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.8.15",
3
+ "version": "3.9.15",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
@@ -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
- }