@electerm/electerm-react 5.3.26 → 5.5.9

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 (146) hide show
  1. package/README.md +246 -12
  2. package/client/common/choose-save-folder.js +21 -2
  3. package/client/common/constants.js +20 -6
  4. package/client/common/create-title.jsx +6 -0
  5. package/client/common/default-setting.js +15 -0
  6. package/client/common/init-setting-item.js +5 -0
  7. package/client/common/platform.js +24 -0
  8. package/client/components/ai/agent-tool-call-card.jsx +10 -2
  9. package/client/components/ai/agent.js +4 -1
  10. package/client/components/ai/ai-attachments.js +87 -0
  11. package/client/components/ai/ai-chat-empty.jsx +43 -0
  12. package/client/components/ai/ai-chat-entry.jsx +13 -4
  13. package/client/components/ai/ai-chat-history-item.jsx +28 -4
  14. package/client/components/ai/ai-chat-history.jsx +2 -1
  15. package/client/components/ai/ai-chat.jsx +104 -13
  16. package/client/components/ai/ai-config-props.js +4 -130
  17. package/client/components/ai/ai-config.jsx +135 -15
  18. package/client/components/ai/ai-presets.js +173 -0
  19. package/client/components/ai/ai.styl +83 -0
  20. package/client/components/bg/css-overwrite.jsx +11 -0
  21. package/client/components/bookmark-form/bookmark-schema.js +4 -0
  22. package/client/components/bookmark-form/common/fields.jsx +3 -0
  23. package/client/components/bookmark-form/config/common-fields.js +12 -0
  24. package/client/components/bookmark-form/config/local.js +3 -2
  25. package/client/components/bookmark-form/config/serial.js +3 -2
  26. package/client/components/bookmark-form/config/ssh.js +2 -1
  27. package/client/components/bookmark-form/config/telnet.js +3 -2
  28. package/client/components/common/item-filter.jsx +76 -0
  29. package/client/components/common/item-filter.styl +54 -0
  30. package/client/components/common/lazy-boundary.jsx +34 -0
  31. package/client/components/common/opacity.jsx +3 -1
  32. package/client/components/footer/cmd-history.jsx +132 -45
  33. package/client/components/footer/cmd-history.styl +42 -4
  34. package/client/components/footer/footer-entry.jsx +35 -5
  35. package/client/components/footer/footer.styl +8 -1
  36. package/client/components/footer/multi-tab-run-modal.jsx +83 -0
  37. package/client/components/footer/tab-select.jsx +34 -17
  38. package/client/components/layout/layout.jsx +23 -31
  39. package/client/components/layout/pixed.js +3 -0
  40. package/client/components/main/main.jsx +55 -28
  41. package/client/components/main/term-fullscreen.styl +9 -3
  42. package/client/components/quick-commands/qm.styl +3 -2
  43. package/client/components/quick-commands/quick-command-create-modal.jsx +60 -0
  44. package/client/components/quick-commands/quick-commands-form-elem.jsx +10 -4
  45. package/client/components/rdp/file-transfer.js +4 -4
  46. package/client/components/remote-monitor/monitor-details.jsx +458 -0
  47. package/client/components/remote-monitor/monitor-details.styl +87 -0
  48. package/client/components/remote-monitor/monitor-model.js +764 -0
  49. package/client/components/remote-monitor/remote-monitor-bar-entry.jsx +18 -0
  50. package/client/components/remote-monitor/remote-monitor-bar.jsx +344 -0
  51. package/client/components/remote-monitor/remote-monitor-bar.styl +106 -0
  52. package/client/components/remote-monitor/session-monitor.js +361 -0
  53. package/client/components/remote-monitor/use-monitor-details.js +10 -0
  54. package/client/components/remote-monitor/visibility.js +18 -0
  55. package/client/components/session/session-control.jsx +29 -2
  56. package/client/components/session/session.jsx +40 -18
  57. package/client/components/session/sessions.jsx +5 -2
  58. package/client/components/setting-panel/setting-common.jsx +25 -56
  59. package/client/components/setting-panel/setting-modal.jsx +57 -42
  60. package/client/components/setting-panel/setting-terminal.jsx +8 -2
  61. package/client/components/setting-panel/setting.styl +1 -1
  62. package/client/components/setting-panel/tab-triggers.jsx +71 -0
  63. package/client/components/sftp/file-compare-modal.jsx +6 -3
  64. package/client/components/sftp/file-item.jsx +5 -3
  65. package/client/components/sftp/owner-list.js +11 -2
  66. package/client/components/sftp/sftp-entry.jsx +4 -0
  67. package/client/components/side-panel-r/side-panel-r.jsx +2 -2
  68. package/client/components/sidebar/side-panel.jsx +1 -1
  69. package/client/components/sys-menu/menu-btn.jsx +30 -1
  70. package/client/components/tabs/index.jsx +2 -2
  71. package/client/components/tabs/tab-title.jsx +3 -2
  72. package/client/components/tabs/tab.jsx +5 -1
  73. package/client/components/terminal/attach-addon-custom.js +295 -49
  74. package/client/components/terminal/automation/index.js +64 -0
  75. package/client/components/terminal/automation/keys.js +49 -0
  76. package/client/components/terminal/automation/stream-matcher.js +82 -0
  77. package/client/components/terminal/automation/strip-ansi.js +19 -0
  78. package/client/components/terminal/automation/trigger-engine.js +233 -0
  79. package/client/components/terminal/automation/trigger-presets.js +110 -0
  80. package/client/components/terminal/command-tracker-addon.js +31 -1
  81. package/client/components/terminal/mixins/index.js +27 -0
  82. package/client/components/terminal/mixins/term-attach.js +123 -0
  83. package/client/components/terminal/mixins/term-config.jsx +84 -0
  84. package/client/components/terminal/mixins/term-content.js +126 -0
  85. package/client/components/terminal/mixins/term-context-menu.jsx +323 -0
  86. package/client/components/terminal/mixins/term-file-drop.js +149 -0
  87. package/client/components/terminal/mixins/term-init.js +122 -0
  88. package/client/components/terminal/mixins/term-log.jsx +96 -0
  89. package/client/components/terminal/mixins/term-resize.js +87 -0
  90. package/client/components/terminal/mixins/term-socket.js +433 -0
  91. package/client/components/terminal/mixins/term-suggestions.js +224 -0
  92. package/client/components/terminal/mixins/term-theme.js +190 -0
  93. package/client/components/terminal/mixins/term-touch.js +523 -0
  94. package/client/components/terminal/shell-detect.js +37 -0
  95. package/client/components/terminal/shell.js +0 -28
  96. package/client/components/terminal/shortcut-bar-defs.js +30 -4
  97. package/client/components/terminal/shortcut-bar-edit.jsx +40 -9
  98. package/client/components/terminal/shortcut-bar-entry.jsx +6 -3
  99. package/client/components/terminal/shortcut-bar.jsx +17 -10
  100. package/client/components/terminal/shortcut-bar.styl +4 -0
  101. package/client/components/terminal/ssh-reload-state.js +96 -0
  102. package/client/components/terminal/startup-queue.js +433 -0
  103. package/client/components/terminal/terminal-interactive.jsx +32 -0
  104. package/client/components/terminal/terminal-select-text.jsx +69 -0
  105. package/client/components/terminal/terminal.jsx +227 -2064
  106. package/client/components/terminal/terminal.styl +61 -0
  107. package/client/components/terminal/transfer-client-base.js +5 -29
  108. package/client/components/terminal/trzsz-client.js +1 -3
  109. package/client/components/terminal/xmodem-client.js +1 -3
  110. package/client/components/terminal/xterm-loader.js +7 -0
  111. package/client/components/terminal/zmodem-client.js +1 -3
  112. package/client/components/terminal-info/base.jsx +18 -70
  113. package/client/components/terminal-info/terminal-info-entry.jsx +13 -5
  114. package/client/components/terminal-info/terminal-info.jsx +50 -59
  115. package/client/components/terminal-info/terminal-info.styl +6 -49
  116. package/client/components/text-editor/open-text-editor.js +30 -0
  117. package/client/components/text-editor/text-editor-entry.jsx +17 -6
  118. package/client/components/text-editor/text-editor.jsx +14 -0
  119. package/client/components/triggers/trigger-editor.jsx +246 -0
  120. package/client/components/triggers/trigger-form.jsx +200 -0
  121. package/client/components/triggers/trigger-session-modal.jsx +199 -0
  122. package/client/components/triggers/trigger-setting-list.jsx +89 -0
  123. package/client/components/triggers/trigger-transport.jsx +60 -0
  124. package/client/components/triggers/triggers-field.jsx +19 -0
  125. package/client/components/widgets/widgets-list.jsx +11 -1
  126. package/client/css/mobile.styl +29 -8
  127. package/client/store/common.js +12 -1
  128. package/client/store/init-state.js +7 -0
  129. package/client/store/mcp-handler.js +158 -30
  130. package/client/store/setting.js +9 -0
  131. package/client/store/store.js +2 -0
  132. package/client/store/sync.js +2 -0
  133. package/client/store/tab.js +42 -3
  134. package/client/store/trigger.js +118 -0
  135. package/client/store/watch.js +13 -5
  136. package/client/views/index.pug +3 -0
  137. package/package.json +1 -1
  138. package/client/components/terminal-info/activity.jsx +0 -57
  139. package/client/components/terminal-info/data-cols-parser.jsx +0 -54
  140. package/client/components/terminal-info/disk.jsx +0 -45
  141. package/client/components/terminal-info/format-disks.js +0 -45
  142. package/client/components/terminal-info/network.jsx +0 -121
  143. package/client/components/terminal-info/resource.jsx +0 -99
  144. package/client/components/terminal-info/run-cmd.jsx +0 -263
  145. package/client/components/terminal-info/sys-info.jsx +0 -56
  146. package/client/components/terminal-info/up.jsx +0 -17
@@ -3,13 +3,24 @@
3
3
  */
4
4
 
5
5
  import { useState, useEffect } from 'react'
6
- import { Button, Empty, Popover } from 'antd'
6
+ import { Button, Empty, Popover, Dropdown } from 'antd'
7
7
  import { auto } from 'manate/react'
8
8
  import SwitchLabel from '../common/switch'
9
9
  import { copy } from '../../common/clipboard'
10
- import { HistoryOutlined, DeleteOutlined, CopyOutlined, UnorderedListOutlined } from '@ant-design/icons'
10
+ import {
11
+ HistoryOutlined,
12
+ DeleteOutlined,
13
+ CopyOutlined,
14
+ UnorderedListOutlined,
15
+ MoreOutlined,
16
+ PlusOutlined,
17
+ CodeOutlined
18
+ } from '@ant-design/icons'
11
19
  import InputAutoFocus from '../common/input-auto-focus'
12
20
  import { getItemJSON, setItemJSON } from '../../common/safe-local-storage'
21
+ import QuickCommandCreateModal from '../quick-commands/quick-command-create-modal'
22
+ import MultiTabRunModal from './multi-tab-run-modal'
23
+ import classNames from 'classnames'
13
24
  import './cmd-history.styl'
14
25
 
15
26
  const e = window.translate
@@ -20,6 +31,10 @@ export default auto(function CmdHistory (props) {
20
31
  const [sortByFrequency, setSortByFrequency] = useState(() => {
21
32
  return getItemJSON(SORT_BY_FREQ_KEY, false)
22
33
  })
34
+ // cmd of the item whose action menu is open / being acted on
35
+ const [menuOpenCmd, setMenuOpenCmd] = useState('')
36
+ const [quickCommandCmd, setQuickCommandCmd] = useState('')
37
+ const [multiTabCmd, setMultiTabCmd] = useState('')
23
38
  const { terminalCommandHistory } = props.store
24
39
 
25
40
  useEffect(() => {
@@ -30,11 +45,6 @@ export default auto(function CmdHistory (props) {
30
45
  window.store.runCmdFromHistory(cmd)
31
46
  }
32
47
 
33
- function handleDeleteCommand (cmd, ev) {
34
- ev.stopPropagation()
35
- window.store.deleteCmdHistory(cmd)
36
- }
37
-
38
48
  function handleCopyCommand (cmd, ev) {
39
49
  ev.stopPropagation()
40
50
  copy(cmd)
@@ -44,6 +54,46 @@ export default auto(function CmdHistory (props) {
44
54
  window.store.clearAllCmdHistory()
45
55
  }
46
56
 
57
+ function handleMenuAction (key, cmd) {
58
+ if (key === 'delete') {
59
+ window.store.deleteCmdHistory(cmd)
60
+ } else if (key === 'quickCommand') {
61
+ setQuickCommandCmd(cmd)
62
+ } else if (key === 'multiTab') {
63
+ window.store.filterBatchInputSelectedTabIds()
64
+ setMultiTabCmd(cmd)
65
+ }
66
+ }
67
+
68
+ function getMenuProps (cmd) {
69
+ return {
70
+ items: [
71
+ {
72
+ key: 'delete',
73
+ icon: <DeleteOutlined />,
74
+ label: e('del'),
75
+ danger: true
76
+ },
77
+ {
78
+ key: 'quickCommand',
79
+ icon: <PlusOutlined />,
80
+ label: e('addQuickCommands')
81
+ },
82
+ {
83
+ key: 'multiTab',
84
+ icon: <CodeOutlined />,
85
+ label: e('runInAllTerminals')
86
+ }
87
+ ],
88
+ onClick: ({ key, domEvent }) => {
89
+ // the menu renders in a portal but React events still bubble through
90
+ // the row, which would run the command
91
+ domEvent.stopPropagation()
92
+ handleMenuAction(key, cmd)
93
+ }
94
+ }
95
+ }
96
+
47
97
  function filterArray (array, keyword) {
48
98
  if (!keyword) {
49
99
  return array
@@ -76,34 +126,46 @@ export default auto(function CmdHistory (props) {
76
126
  />
77
127
  )
78
128
  }
79
- return filtered.map((item, index) => (
80
- <div
81
- key={index}
82
- className='cmd-history-item'
83
- onClick={() => handleRunCommand(item.cmd)}
84
- >
85
- <span className='cmd-history-item-text' title={item.cmd}>{item.cmd}</span>
86
- <div className='cmd-history-item-actions'>
87
- <span className='cmd-history-item-count' title={e('count') + ': ' + item.count}>
88
- {item.count}
89
- </span>
90
- <Button
91
- type='text'
92
- size='small'
93
- icon={<CopyOutlined />}
94
- className='cmd-history-item-copy'
95
- onClick={(ev) => handleCopyCommand(item.cmd, ev)}
96
- />
97
- <Button
98
- type='text'
99
- size='small'
100
- icon={<DeleteOutlined />}
101
- className='cmd-history-item-delete'
102
- onClick={(ev) => handleDeleteCommand(item.cmd, ev)}
103
- />
129
+ return filtered.map((item, index) => {
130
+ const cls = classNames(
131
+ 'cmd-history-item',
132
+ { 'menu-open': menuOpenCmd === item.cmd }
133
+ )
134
+ return (
135
+ <div
136
+ key={index}
137
+ className={cls}
138
+ onClick={() => handleRunCommand(item.cmd)}
139
+ >
140
+ <span className='cmd-history-item-text' title={item.cmd}>{item.cmd}</span>
141
+ <div className='cmd-history-item-actions'>
142
+ <span className='cmd-history-item-count' title={e('count') + ': ' + item.count}>
143
+ {item.count}
144
+ </span>
145
+ <Button
146
+ type='text'
147
+ size='small'
148
+ icon={<CopyOutlined />}
149
+ className='cmd-history-item-copy'
150
+ onClick={(ev) => handleCopyCommand(item.cmd, ev)}
151
+ />
152
+ <Dropdown
153
+ menu={getMenuProps(item.cmd)}
154
+ trigger={['click']}
155
+ onOpenChange={(open) => setMenuOpenCmd(open ? item.cmd : '')}
156
+ >
157
+ <Button
158
+ type='text'
159
+ size='small'
160
+ icon={<MoreOutlined />}
161
+ className='cmd-history-item-more'
162
+ onClick={(ev) => ev.stopPropagation()}
163
+ />
164
+ </Dropdown>
165
+ </div>
104
166
  </div>
105
- </div>
106
- ))
167
+ )
168
+ })
107
169
  }
108
170
 
109
171
  function renderHeader () {
@@ -148,16 +210,41 @@ export default auto(function CmdHistory (props) {
148
210
  )
149
211
 
150
212
  return (
151
- <Popover
152
- content={content}
153
- trigger='click'
154
- placement='topLeft'
155
- >
156
- <Button
157
- size='small'
158
- type='text'
159
- icon={<HistoryOutlined />}
160
- />
161
- </Popover>
213
+ <>
214
+ <Popover
215
+ content={content}
216
+ trigger='click'
217
+ placement='topLeft'
218
+ >
219
+ <Button
220
+ size='small'
221
+ type='text'
222
+ icon={<HistoryOutlined />}
223
+ />
224
+ </Popover>
225
+ {
226
+ quickCommandCmd
227
+ ? (
228
+ <QuickCommandCreateModal
229
+ key={quickCommandCmd}
230
+ store={props.store}
231
+ command={quickCommandCmd}
232
+ onClose={() => setQuickCommandCmd('')}
233
+ />
234
+ )
235
+ : null
236
+ }
237
+ {
238
+ multiTabCmd
239
+ ? (
240
+ <MultiTabRunModal
241
+ store={props.store}
242
+ cmd={multiTabCmd}
243
+ onClose={() => setMultiTabCmd('')}
244
+ />
245
+ )
246
+ : null
247
+ }
248
+ </>
162
249
  )
163
250
  })
@@ -4,7 +4,6 @@
4
4
  overflow hidden
5
5
  display flex
6
6
  flex-direction column
7
-
8
7
  .cmd-history-search
9
8
  flex-shrink 0
10
9
  position sticky
@@ -46,6 +45,13 @@
46
45
  cursor pointer
47
46
  &:hover
48
47
  background var(--main-lighter)
48
+ // keep the row actions visible while its action menu is open, otherwise the
49
+ // menu trigger would be hidden (display none) and the popup would lose its
50
+ // anchor when the mouse moves onto the menu
51
+ &.menu-open
52
+ background var(--main-lighter)
53
+ .cmd-history-item-actions
54
+ display flex
49
55
 
50
56
  .cmd-history-item-text
51
57
  overflow hidden
@@ -68,14 +74,46 @@
68
74
  .cmd-history-item:hover &
69
75
  display flex
70
76
 
71
- .cmd-history-item-delete
77
+ .cmd-history-item-more
72
78
  cursor pointer
73
79
  padding 2px 4px
74
- &:hover
75
- color red
76
80
 
77
81
  .cmd-history-item-copy
78
82
  cursor pointer
79
83
  padding 2px 4px
80
84
  &:hover
81
85
  color blue
86
+
87
+ .multi-tab-run-cmd
88
+ font-size 12px
89
+ padding 6px 8px
90
+ margin-bottom 10px
91
+ border-radius 4px
92
+ background var(--main-lighter)
93
+ word-break break-all
94
+ max-height 96px
95
+ overflow-y auto
96
+
97
+ // Narrow screens: the popup is aligned to the history trigger (placement
98
+ // topLeft) and is 324px wide, while the footer trigger sits within the first
99
+ // ~96px of the screen (43px icon bar + footer margin + the icons before it), so
100
+ // below ~420px the popup's right edge would fall outside the window. antd's
101
+ // auto-shift does not correct it, so pin the popup to the viewport instead.
102
+ // Matched by its content: the popup lives in a body-level portal, so neither
103
+ // the component tree nor an antd popup class reaches it reliably.
104
+ @media (max-width: 420px)
105
+ .ant-popover:has(.cmd-history-popover-content)
106
+ // rc-trigger writes `inset: auto auto 40px <trigger left>px` inline
107
+ left 0px !important
108
+ // pinned to the viewport, the arrow no longer points at the trigger
109
+ .ant-popover-arrow
110
+ display none
111
+ .cmd-history-popover-content
112
+ // popup = content + 24px container padding; keep 8px on both sides
113
+ width 100%
114
+
115
+ // Touch devices have no hover, so the row actions (count / copy / ⋯ menu)
116
+ // would never be reachable — feature-detect instead of sniffing the UA
117
+ @media (pointer: coarse)
118
+ .cmd-history-item-actions
119
+ display flex
@@ -1,25 +1,27 @@
1
1
  import { auto } from 'manate/react'
2
2
  import {
3
3
  Select,
4
- Dropdown
4
+ Dropdown,
5
+ Badge
5
6
  } from 'antd'
6
- import { InfoCircleOutlined, TranslationOutlined, DoubleRightOutlined } from '@ant-design/icons'
7
+ import { BarChartOutlined, TranslationOutlined, DoubleRightOutlined, FunctionOutlined } from '@ant-design/icons'
7
8
  import './footer.styl'
8
9
  import { statusMap } from '../../common/constants'
9
10
  import BatchInput from './batch-input'
10
11
  import encodes from '../bookmark-form/common/encodes'
11
12
  import { refs } from '../common/ref'
12
13
  import Qm from '../quick-commands/quick-commands-select'
14
+ import TriggerSessionModal from '../triggers/trigger-session-modal'
13
15
  import AIIcon from '../icons/ai-icon'
14
16
  import { isAIDisabled } from '../../common/ai-feature'
15
17
  import CmdHistory from './cmd-history'
16
18
 
19
+ const e = window.translate
20
+
17
21
  const {
18
22
  Option
19
23
  } = Select
20
24
 
21
- const e = window.translate
22
-
23
25
  export default auto(function FooterEntry (props) {
24
26
  function handleInfoPanel () {
25
27
  window.store.openInfoPanel()
@@ -77,6 +79,33 @@ export default auto(function FooterEntry (props) {
77
79
  )
78
80
  }
79
81
 
82
+ function renderTriggers () {
83
+ const { store } = props
84
+ const tab = store.currentTab
85
+ let count = 0
86
+ try {
87
+ count = tab ? store.getEffectiveTriggers(tab).length : store.triggers.length
88
+ } catch (err) {
89
+ count = 0
90
+ }
91
+ return (
92
+ <div className='terminal-footer-unit terminal-footer-triggers'>
93
+ <Badge
94
+ count={count}
95
+ size='small'
96
+ offset={[-2, 2]}
97
+ >
98
+ <FunctionOutlined
99
+ onClick={() => store.toggleTriggerSessionModal(true)}
100
+ className='pointer font14 terminal-trigger-icon'
101
+ title={e('triggers')}
102
+ />
103
+ </Badge>
104
+ <TriggerSessionModal store={store} />
105
+ </div>
106
+ )
107
+ }
108
+
80
109
  function renderAIIcon () {
81
110
  return (
82
111
  <div className='terminal-footer-unit terminal-footer-ai'>
@@ -149,7 +178,7 @@ export default auto(function FooterEntry (props) {
149
178
  }
150
179
  return (
151
180
  <div className='terminal-footer-unit terminal-footer-info'>
152
- <InfoCircleOutlined
181
+ <BarChartOutlined
153
182
  onClick={handleInfoPanel}
154
183
  className='pointer font14 terminal-info-icon'
155
184
  />
@@ -214,6 +243,7 @@ export default auto(function FooterEntry (props) {
214
243
  {!isAIDisabled() && renderAIIcon()}
215
244
  {renderCmdHistory()}
216
245
  {renderQuickCommands()}
246
+ {renderTriggers()}
217
247
  {renderBatchInputs()}
218
248
  {renderEncodingInfo()}
219
249
  {renderInfoIcon()}
@@ -38,6 +38,9 @@
38
38
  // Q is now an icon-only button (5px internal padding); reduce left
39
39
  // margin so the history icon stays roughly equidistant from AI and Q
40
40
  margin-left 3px
41
+ .terminal-footer-triggers
42
+ display flex
43
+ align-items center
41
44
  .batch-input-outer
42
45
  position relative
43
46
  .bi-full
@@ -61,4 +64,8 @@
61
64
  border-radius 4px 4px 0 0
62
65
  min-width 360px
63
66
  .batch-input-holder
64
- width 100px
67
+ width 100px
68
+ .remote-monitor-bar-on
69
+ .batch-input-outer.bi-show
70
+ .bi-full
71
+ bottom calc(100% + 28px)
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Run one command (usually picked from the command history) in several
3
+ * terminals at once.
4
+ *
5
+ * Reuses the footer batch input tab panel (`TabSelectList`) for picking the
6
+ * target terminals, and the same selection state + `batchInput` terminal API
7
+ * the footer batch input uses, so both entry points stay consistent.
8
+ */
9
+
10
+ import { auto } from 'manate/react'
11
+ import {
12
+ Button,
13
+ Modal
14
+ } from 'antd'
15
+ import { TabSelectList } from './tab-select'
16
+ import { refs } from '../common/ref'
17
+ import {
18
+ terminalWebType,
19
+ terminalRdpType,
20
+ terminalVncType
21
+ } from '../../common/constants'
22
+
23
+ const e = window.translate
24
+
25
+ export default auto(function MultiTabRunModal (props) {
26
+ const { store, cmd, onClose } = props
27
+ const selectedTabIds = store.batchInputSelectedTabIds
28
+ const tabs = store.tabs.filter(tab => {
29
+ return tab.type !== terminalWebType &&
30
+ tab.type !== terminalRdpType &&
31
+ tab.type !== terminalVncType
32
+ }).sort((a, b) => {
33
+ // current tab goes first
34
+ if (a.id === store.activeTabId) return -1
35
+ if (b.id === store.activeTabId) return 1
36
+ return 0
37
+ })
38
+ const listProps = {
39
+ tabs,
40
+ activeTabId: store.activeTabId,
41
+ selectedTabIds,
42
+ onSelect: store.onSelectBatchInputSelectedTabId,
43
+ onSelectAll: store.selectAllBatchInputTabs,
44
+ onSelectNone: store.selectNoneBatchInputTabs
45
+ }
46
+ function handleRun () {
47
+ selectedTabIds.map(id => {
48
+ return refs.get('term-' + id)
49
+ }).forEach(term => {
50
+ term?.batchInput(cmd)
51
+ })
52
+ store.addCmdHistory(cmd)
53
+ onClose()
54
+ }
55
+ return (
56
+ <Modal
57
+ open
58
+ title={e('runInAllTerminals')}
59
+ onCancel={onClose}
60
+ destroyOnHidden
61
+ width={520}
62
+ footer={[
63
+ <Button
64
+ key='cancel'
65
+ onClick={onClose}
66
+ >
67
+ {e('cancel')}
68
+ </Button>,
69
+ <Button
70
+ key='run'
71
+ type='primary'
72
+ disabled={!selectedTabIds.length}
73
+ onClick={handleRun}
74
+ >
75
+ {e('ok')}
76
+ </Button>
77
+ ]}
78
+ >
79
+ <div className='multi-tab-run-cmd'>{cmd}</div>
80
+ <TabSelectList {...listProps} />
81
+ </Modal>
82
+ )
83
+ })
@@ -6,15 +6,28 @@ import {
6
6
  CodeOutlined
7
7
  } from '@ant-design/icons'
8
8
 
9
- export default function TabSelect (props) {
10
- const { selectedTabIds, tabs, activeTabId } = props
9
+ /**
10
+ * The multi terminal selection panel: select-all/none plus one toggle button
11
+ * per terminal tab.
12
+ * Extracted so it can be reused outside the batch input popover (e.g. run a
13
+ * command from the history panel in several terminals at once).
14
+ */
15
+ export function TabSelectList (props) {
16
+ const {
17
+ selectedTabIds = [],
18
+ tabs = [],
19
+ activeTabId,
20
+ onSelect,
21
+ onSelectAll,
22
+ onSelectNone
23
+ } = props
11
24
  function renderTabs () {
12
25
  return tabs.map(tab => {
13
26
  const selected = selectedTabIds.includes(tab.id)
14
27
  const itemProps = {
15
28
  tab,
16
29
  selected,
17
- onSelect: window.store.onSelectBatchInputSelectedTabId,
30
+ onSelect,
18
31
  id: tab.id,
19
32
  isCurrent: tab.id === activeTabId
20
33
  }
@@ -26,12 +39,6 @@ export default function TabSelect (props) {
26
39
  )
27
40
  })
28
41
  }
29
- function onSelectAll () {
30
- window.store.selectAllBatchInputTabs()
31
- }
32
- function onSelectNone () {
33
- window.store.selectNoneBatchInputTabs()
34
- }
35
42
  function renderBtns () {
36
43
  return (
37
44
  <div className='pd1t pd2b font12'>
@@ -50,17 +57,27 @@ export default function TabSelect (props) {
50
57
  </div>
51
58
  )
52
59
  }
53
- function renderContent () {
54
- return (
55
- <div className='pd1x alignright'>
56
- {renderBtns()}
57
- {renderTabs()}
58
- </div>
59
- )
60
+ return (
61
+ <div className='pd1x alignright'>
62
+ {renderBtns()}
63
+ {renderTabs()}
64
+ </div>
65
+ )
66
+ }
67
+
68
+ export default function TabSelect (props) {
69
+ const { selectedTabIds, tabs, activeTabId } = props
70
+ const listProps = {
71
+ selectedTabIds,
72
+ tabs,
73
+ activeTabId,
74
+ onSelect: window.store.onSelectBatchInputSelectedTabId,
75
+ onSelectAll: window.store.selectAllBatchInputTabs,
76
+ onSelectNone: window.store.selectNoneBatchInputTabs
60
77
  }
61
78
  return (
62
79
  <Popover
63
- content={renderContent()}
80
+ content={<TabSelectList {...listProps} />}
64
81
  trigger='click'
65
82
  >
66
83
  <span className='pointer iblock pd1x'>
@@ -5,12 +5,15 @@ import {
5
5
  splitConfig,
6
6
  quickCommandBoxHeight,
7
7
  footerHeight,
8
+ remoteMonitorBarHeight,
8
9
  shortcutBarHeight
9
10
  } from '../../common/constants'
10
11
  import layoutAlg from './layout-alg'
11
12
  import calcSessionSize from './session-size-alg'
12
13
  import TermSearch from '../terminal/term-search'
13
14
  import Footer from '../footer/footer-entry'
15
+ import RemoteMonitorBar from '../remote-monitor/remote-monitor-bar-entry'
16
+ import { isRemoteMonitorBarVisible } from '../remote-monitor/visibility'
14
17
  import SessionsWrap from '../session/sessions'
15
18
  import QuickCommandsFooterBox from '../quick-commands/quick-commands-box'
16
19
  import pixed from './pixed'
@@ -32,11 +35,10 @@ export default auto(function Layout (props) {
32
35
  const {
33
36
  width,
34
37
  height,
38
+ isMobile,
35
39
  pinnedQuickCommandBar,
36
- // tabsHeight,
37
40
  leftSidePanelWidth,
38
41
  leftSideBarWidth,
39
- // infoPanelPinned,
40
42
  pinned,
41
43
  rightPanelVisible,
42
44
  rightPanelPinned,
@@ -46,47 +48,25 @@ export default auto(function Layout (props) {
46
48
  shortcutBarVisible,
47
49
  shortcutBarKbOffset
48
50
  } = props.store
49
- const h = height - footerHeight - (inActiveTerminal && pinnedQuickCommandBar ? quickCommandBoxHeight : 0) - (shortcutBarVisible ? shortcutBarHeight + shortcutBarKbOffset : 0) + resizeTrigger
50
- const l = pinned ? leftSideBarWidth + leftSidePanelWidth : leftSideBarWidth
51
- const r = rightPanelVisible && rightPanelPinned ? rightPanelWidth : 0
51
+ const monitorHeight = isRemoteMonitorBarVisible(props.store) ? remoteMonitorBarHeight : 0
52
+ const h = height - footerHeight - monitorHeight - (inActiveTerminal && pinnedQuickCommandBar ? quickCommandBoxHeight : 0) - (shortcutBarVisible ? shortcutBarHeight + shortcutBarKbOffset : 0) + resizeTrigger
53
+ const l = pinned && !isMobile ? leftSideBarWidth + leftSidePanelWidth : leftSideBarWidth
54
+ const r = rightPanelVisible && rightPanelPinned && !isMobile ? rightPanelWidth : 0
52
55
  return {
53
56
  height: h,
54
57
  top: 0,
55
58
  left: l,
56
- width: width - l - r
59
+ width: Math.max(0, width - l - r)
57
60
  }
58
61
  }
59
62
 
60
- const buildLayoutStyles = () => {
61
- const {
62
- layout,
63
- height,
64
- width,
65
- pinnedQuickCommandBar,
66
- leftSidePanelWidth,
67
- leftSideBarWidth,
68
- rightPanelVisible,
69
- rightPanelPinned,
70
- rightPanelWidth,
71
- pinned,
72
- shortcutBarVisible,
73
- shortcutBarKbOffset
74
- } = props.store
75
- const l = pinned ? leftSidePanelWidth : 0
76
- const r = rightPanelPinned && rightPanelVisible ? rightPanelWidth : 0
77
- // account for the far-left icon bar (sidebarWidth - 1px border on desktop;
78
- // 0 when the bar is hidden on mobile)
79
- const w = width - l - r - (leftSideBarWidth > 0 ? leftSideBarWidth - 1 : 0)
80
- const h = height - footerHeight - (pinnedQuickCommandBar ? quickCommandBoxHeight : 0) - (shortcutBarVisible ? shortcutBarHeight + shortcutBarKbOffset : 0)
81
- return layoutAlg(layout, w, h)
82
- }
83
63
  const layoutSize = calcLayoutStyle()
84
64
  const {
85
65
  width,
86
66
  height
87
67
  } = layoutSize
88
68
  const pixedLayoutStyle = pixed(layoutSize)
89
- const styles = buildLayoutStyles(conf, layout)
69
+ const styles = layoutAlg(layout, width, height)
90
70
  const layoutProps = {
91
71
  layout,
92
72
  ...styles,
@@ -102,7 +82,14 @@ export default auto(function Layout (props) {
102
82
  const { tabs } = store
103
83
  const tabsBatch = {}
104
84
  for (const tab of tabs) {
105
- const { batch } = tab
85
+ let { batch } = tab
86
+ // Guard against tabs with missing/invalid batch (e.g. created by
87
+ // MCP/AI operations before sanitization). Route them to pane 0
88
+ // instead of crashing or disappearing.
89
+ batch = Number(batch)
90
+ if (!Number.isInteger(batch) || batch < 0 || batch >= sizes.length) {
91
+ batch = 0
92
+ }
106
93
  if (!tabsBatch[batch]) {
107
94
  tabsBatch[batch] = []
108
95
  }
@@ -207,6 +194,11 @@ export default auto(function Layout (props) {
207
194
  key='QuickCommandsFooterBox'
208
195
  {...qmProps}
209
196
  />,
197
+ <RemoteMonitorBar
198
+ key='RemoteMonitorBar'
199
+ store={store}
200
+ style={{ left: layoutSize.left, width, height: remoteMonitorBarHeight }}
201
+ />,
210
202
  <Footer
211
203
  key='Footer'
212
204
  {...footerProps}
@@ -1,4 +1,7 @@
1
1
  export default function pixed (style) {
2
+ if (!style || typeof style !== 'object') {
3
+ return {}
4
+ }
2
5
  return Object.keys(style).reduce((prev, k) => {
3
6
  const v = style[k]
4
7
  return {