@electerm/electerm-react 3.15.175 → 3.15.186
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/bookmark-schemas.js +2 -0
- package/client/common/drop-position.js +59 -0
- package/client/components/bookmark-form/bookmark-schema.js +2 -0
- package/client/components/bookmark-form/common/connection-hopping.jsx +33 -7
- package/client/components/bookmark-form/common/ssh-tunnel-form.jsx +25 -7
- package/client/components/bookmark-form/common/ssh-tunnels.jsx +1 -1
- package/client/components/bookmark-form/config/serial.js +14 -0
- package/client/components/main/term-fullscreen.styl +5 -0
- package/client/components/quick-commands/on-drop.js +6 -1
- package/client/components/quick-commands/quick-commands-box.jsx +18 -2
- package/client/components/quick-commands/quick-commands-list-form.jsx +15 -4
- package/client/components/quick-commands/quick-commands-list.jsx +18 -2
- package/client/components/session/session-control.jsx +331 -0
- package/client/components/session/session-control.styl +38 -0
- package/client/components/session/session.jsx +41 -246
- package/client/components/session/session.styl +1 -0
- package/client/components/sftp/address-bookmark-item.jsx +15 -2
- package/client/components/sftp/code-compare-utils.js +260 -0
- package/client/components/sftp/code-compare.jsx +183 -0
- package/client/components/sftp/code-compare.styl +13 -0
- package/client/components/sftp/file-compare-modal.jsx +87 -42
- package/client/components/shortcuts/shortcut-handler.js +20 -0
- package/client/components/sidebar/transport-ui.jsx +21 -14
- package/client/components/tabs/index.jsx +18 -3
- package/client/components/tabs/tab.jsx +27 -8
- package/client/components/tabs/tabs.styl +8 -0
- package/client/components/terminal/attach-addon-custom.js +147 -36
- package/client/components/terminal/highlight-addon.js +26 -8
- package/client/components/terminal/term-search.jsx +1 -1
- package/client/components/terminal/terminal.jsx +193 -2
- package/client/components/terminal/terminal.styl +0 -2
- package/client/components/terminal-info/run-cmd.jsx +1 -1
- package/client/components/tree-list/tree-list.jsx +57 -39
- package/client/css/basic.styl +14 -0
- package/client/css/mobile.styl +31 -9
- package/client/store/item.js +10 -6
- package/package.json +1 -1
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session control bar — pane tabs and action icons.
|
|
3
|
+
* On mobile a menu icon is shown; clicking it toggles the control icons.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
SearchOutlined,
|
|
7
|
+
FullscreenOutlined,
|
|
8
|
+
PaperClipOutlined,
|
|
9
|
+
CloseOutlined,
|
|
10
|
+
ApartmentOutlined,
|
|
11
|
+
MoreOutlined,
|
|
12
|
+
ColumnWidthOutlined,
|
|
13
|
+
LogoutOutlined
|
|
14
|
+
} from '@ant-design/icons'
|
|
15
|
+
import { Tooltip, Popover } from 'antd'
|
|
16
|
+
import classnames from 'classnames'
|
|
17
|
+
import {
|
|
18
|
+
paneMap,
|
|
19
|
+
connectionMap,
|
|
20
|
+
terminalSerialType
|
|
21
|
+
} from '../../common/constants'
|
|
22
|
+
import { SplitViewIcon } from '../icons/split-view'
|
|
23
|
+
import { HeartbeatIcon } from '../icons/heartbeat'
|
|
24
|
+
import './session-control.styl'
|
|
25
|
+
|
|
26
|
+
const e = window.translate
|
|
27
|
+
|
|
28
|
+
export default function SessionControl (props) {
|
|
29
|
+
const {
|
|
30
|
+
tab,
|
|
31
|
+
isMobile,
|
|
32
|
+
isDisabled,
|
|
33
|
+
isSshDisabled,
|
|
34
|
+
isNotTerminalType,
|
|
35
|
+
canSplitView,
|
|
36
|
+
sftpPathFollowSsh,
|
|
37
|
+
keepaliveEnabled,
|
|
38
|
+
broadcastInput,
|
|
39
|
+
wrapDisabled,
|
|
40
|
+
delKeyPressed,
|
|
41
|
+
hideDelKeyTip,
|
|
42
|
+
onChangePane,
|
|
43
|
+
toggleCheckSftpPathFollowSsh,
|
|
44
|
+
onSshSftpSplitView,
|
|
45
|
+
toggleKeepalive,
|
|
46
|
+
toggleBroadcastInput,
|
|
47
|
+
toggleWrap,
|
|
48
|
+
onFullscreen,
|
|
49
|
+
onOpenSearch,
|
|
50
|
+
onDismissDelKeyTip,
|
|
51
|
+
onExitGracefully
|
|
52
|
+
} = props
|
|
53
|
+
|
|
54
|
+
if (isNotTerminalType) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const isSsh = !!tab.authType
|
|
59
|
+
const isLocal = !isSsh && (tab.type === connectionMap.local || !tab.type)
|
|
60
|
+
const showSshFeatures = isSsh || isLocal
|
|
61
|
+
|
|
62
|
+
// ---- sub-renderers ----
|
|
63
|
+
|
|
64
|
+
function renderPaneControl () {
|
|
65
|
+
if (isDisabled) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
const { sshSftpSplitView, pane } = tab
|
|
69
|
+
if (sshSftpSplitView && canSplitView) {
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
const types = [
|
|
73
|
+
paneMap.terminal,
|
|
74
|
+
paneMap.fileManager
|
|
75
|
+
]
|
|
76
|
+
const controls = [
|
|
77
|
+
isSsh ? paneMap.ssh : paneMap.terminal
|
|
78
|
+
]
|
|
79
|
+
if (isSsh || isLocal) {
|
|
80
|
+
controls.push(isSsh ? paneMap.sftp : paneMap.fileManager)
|
|
81
|
+
}
|
|
82
|
+
return (
|
|
83
|
+
<div className='term-sftp-tabs fleft'>
|
|
84
|
+
{
|
|
85
|
+
controls.map((type, i) => {
|
|
86
|
+
const cls = classnames(
|
|
87
|
+
'type-tab',
|
|
88
|
+
type,
|
|
89
|
+
{
|
|
90
|
+
active: types[i] === pane
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
return (
|
|
94
|
+
<span
|
|
95
|
+
className={cls}
|
|
96
|
+
key={type + '_' + i}
|
|
97
|
+
onClick={() => onChangePane(types[i])}
|
|
98
|
+
>
|
|
99
|
+
<span className='type-tab-txt'>
|
|
100
|
+
{e(type)}
|
|
101
|
+
<span className='type-tab-line' />
|
|
102
|
+
</span>
|
|
103
|
+
</span>
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function renderDelTip (isS) {
|
|
112
|
+
if (!isS || hideDelKeyTip || !delKeyPressed) {
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
return (
|
|
116
|
+
<div className='type-tab'>
|
|
117
|
+
<span className='mg1r'>Try <b>Shift + Backspace</b>?</span>
|
|
118
|
+
<CloseOutlined
|
|
119
|
+
onClick={onDismissDelKeyTip}
|
|
120
|
+
className='pointer'
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function renderSftpPathFollowControl () {
|
|
127
|
+
if (isDisabled) {
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
const { pane, enableSsh, sshSftpSplitView } = tab
|
|
131
|
+
const checkTxt = e('sftpPathFollowSsh')
|
|
132
|
+
const checkProps = {
|
|
133
|
+
onClick: toggleCheckSftpPathFollowSsh,
|
|
134
|
+
className: classnames(
|
|
135
|
+
'sftp-follow-ssh-icon sess-icon pointer',
|
|
136
|
+
{
|
|
137
|
+
active: sftpPathFollowSsh
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
const isS = pane === paneMap.terminal ||
|
|
142
|
+
sshSftpSplitView
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
{
|
|
146
|
+
(isSsh && enableSsh) || isLocal
|
|
147
|
+
? (
|
|
148
|
+
<Tooltip title={checkTxt}>
|
|
149
|
+
<span {...checkProps}>
|
|
150
|
+
<PaperClipOutlined />
|
|
151
|
+
</span>
|
|
152
|
+
</Tooltip>
|
|
153
|
+
)
|
|
154
|
+
: null
|
|
155
|
+
}
|
|
156
|
+
{renderDelTip(isS)}
|
|
157
|
+
</>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function renderSplitToggle () {
|
|
162
|
+
if (isMobile) {
|
|
163
|
+
return null
|
|
164
|
+
}
|
|
165
|
+
if (!canSplitView || isNotTerminalType || !showSshFeatures) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
const title = e('sshSftpSplitView')
|
|
169
|
+
const { sshSftpSplitView } = tab
|
|
170
|
+
const cls = classnames(
|
|
171
|
+
'pointer sess-icon split-view-toggle',
|
|
172
|
+
{
|
|
173
|
+
active: sshSftpSplitView
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
return (
|
|
177
|
+
<Tooltip title={title} placement='bottomLeft'>
|
|
178
|
+
<span
|
|
179
|
+
className={cls}
|
|
180
|
+
onClick={onSshSftpSplitView}
|
|
181
|
+
>
|
|
182
|
+
<SplitViewIcon />
|
|
183
|
+
</span>
|
|
184
|
+
</Tooltip>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function renderKeepaliveIcon () {
|
|
189
|
+
if (isSshDisabled || !showSshFeatures) {
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
const title = e('keepalive')
|
|
193
|
+
const iconProps = {
|
|
194
|
+
className: classnames('sess-icon pointer keepalive-icon', {
|
|
195
|
+
active: keepaliveEnabled
|
|
196
|
+
}),
|
|
197
|
+
onClick: toggleKeepalive
|
|
198
|
+
}
|
|
199
|
+
return (
|
|
200
|
+
<Tooltip title={title}>
|
|
201
|
+
<HeartbeatIcon {...iconProps} />
|
|
202
|
+
</Tooltip>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function renderBroadcastIcon () {
|
|
207
|
+
if (isSshDisabled || !showSshFeatures) {
|
|
208
|
+
return null
|
|
209
|
+
}
|
|
210
|
+
const title = e('broadcastInput')
|
|
211
|
+
const iconProps = {
|
|
212
|
+
className: classnames('sess-icon pointer broadcast-icon', {
|
|
213
|
+
active: broadcastInput
|
|
214
|
+
}),
|
|
215
|
+
onClick: toggleBroadcastInput
|
|
216
|
+
}
|
|
217
|
+
return (
|
|
218
|
+
<Tooltip title={title}>
|
|
219
|
+
<ApartmentOutlined {...iconProps} />
|
|
220
|
+
</Tooltip>
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function renderWrapIcon () {
|
|
225
|
+
const title = e(wrapDisabled ? 'enableWrap' : 'disableWrap')
|
|
226
|
+
const iconProps = {
|
|
227
|
+
className: classnames('sess-icon pointer wrap-toggle-icon', {
|
|
228
|
+
active: wrapDisabled
|
|
229
|
+
}),
|
|
230
|
+
onClick: toggleWrap
|
|
231
|
+
}
|
|
232
|
+
return (
|
|
233
|
+
<Tooltip title={title}>
|
|
234
|
+
<ColumnWidthOutlined {...iconProps} />
|
|
235
|
+
</Tooltip>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function renderExitGracefullyIcon () {
|
|
240
|
+
if (tab.type !== terminalSerialType) {
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
const title = e('exitGracefully')
|
|
244
|
+
return (
|
|
245
|
+
<Tooltip title={title}>
|
|
246
|
+
<LogoutOutlined
|
|
247
|
+
className='sess-icon pointer exit-gracefully-icon'
|
|
248
|
+
onClick={onExitGracefully}
|
|
249
|
+
/>
|
|
250
|
+
</Tooltip>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function renderSearchIcon () {
|
|
255
|
+
const title = e('search')
|
|
256
|
+
return (
|
|
257
|
+
<Tooltip title={title} placement='bottomLeft'>
|
|
258
|
+
<SearchOutlined
|
|
259
|
+
className='mg1r icon-info iblock pointer spliter'
|
|
260
|
+
onClick={onOpenSearch}
|
|
261
|
+
/>
|
|
262
|
+
</Tooltip>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderFullscreenIcon () {
|
|
267
|
+
const title = e('fullscreen')
|
|
268
|
+
return (
|
|
269
|
+
<Tooltip title={title} placement='bottomLeft'>
|
|
270
|
+
<FullscreenOutlined
|
|
271
|
+
className='mg1r icon-info iblock pointer spliter fullscreen-control-icon'
|
|
272
|
+
onClick={onFullscreen}
|
|
273
|
+
/>
|
|
274
|
+
</Tooltip>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function renderTermControls () {
|
|
279
|
+
const { pane } = tab
|
|
280
|
+
if (pane !== paneMap.terminal) {
|
|
281
|
+
return null
|
|
282
|
+
}
|
|
283
|
+
return (
|
|
284
|
+
<div className='fright term-controls'>
|
|
285
|
+
{renderFullscreenIcon()}
|
|
286
|
+
{renderSearchIcon()}
|
|
287
|
+
</div>
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ---- mobile ----
|
|
292
|
+
|
|
293
|
+
if (isMobile) {
|
|
294
|
+
const extraIcons = (
|
|
295
|
+
<div className='mobile-control-icons'>
|
|
296
|
+
{renderSftpPathFollowControl()}
|
|
297
|
+
{renderKeepaliveIcon()}
|
|
298
|
+
{renderBroadcastIcon()}
|
|
299
|
+
{renderWrapIcon()}
|
|
300
|
+
{renderExitGracefullyIcon()}
|
|
301
|
+
{renderTermControls()}
|
|
302
|
+
</div>
|
|
303
|
+
)
|
|
304
|
+
return (
|
|
305
|
+
<div className='terminal-control mobile-session-control'>
|
|
306
|
+
{renderPaneControl()}
|
|
307
|
+
<Popover
|
|
308
|
+
content={extraIcons}
|
|
309
|
+
trigger='click'
|
|
310
|
+
placement='bottomRight'
|
|
311
|
+
>
|
|
312
|
+
<MoreOutlined className='mobile-control-toggle pointer' />
|
|
313
|
+
</Popover>
|
|
314
|
+
</div>
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ---- desktop ----
|
|
319
|
+
return (
|
|
320
|
+
<div className='terminal-control fix'>
|
|
321
|
+
{renderPaneControl()}
|
|
322
|
+
{renderSftpPathFollowControl()}
|
|
323
|
+
{renderSplitToggle()}
|
|
324
|
+
{renderKeepaliveIcon()}
|
|
325
|
+
{renderBroadcastIcon()}
|
|
326
|
+
{renderWrapIcon()}
|
|
327
|
+
{renderExitGracefullyIcon()}
|
|
328
|
+
{renderTermControls()}
|
|
329
|
+
</div>
|
|
330
|
+
)
|
|
331
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Mobile session control: pane tabs always visible,
|
|
2
|
+
// other icons shown in a floating Popover overlay
|
|
3
|
+
.mobile-session-control
|
|
4
|
+
background var(--main)
|
|
5
|
+
line-height 32px
|
|
6
|
+
padding 0 10px
|
|
7
|
+
display flex
|
|
8
|
+
align-items center
|
|
9
|
+
// pane tabs take available space, menu icon sits at the end
|
|
10
|
+
.term-sftp-tabs
|
|
11
|
+
float none
|
|
12
|
+
display flex
|
|
13
|
+
align-items center
|
|
14
|
+
flex 1
|
|
15
|
+
min-width 0
|
|
16
|
+
.mobile-control-toggle
|
|
17
|
+
font-size 16px
|
|
18
|
+
line-height 32px
|
|
19
|
+
color var(--text)
|
|
20
|
+
flex-shrink 0
|
|
21
|
+
&:hover
|
|
22
|
+
color var(--text-light)
|
|
23
|
+
|
|
24
|
+
// Popover content — icons laid out in a row, wrapping as needed
|
|
25
|
+
.mobile-control-icons
|
|
26
|
+
display flex
|
|
27
|
+
flex-wrap wrap
|
|
28
|
+
align-items center
|
|
29
|
+
gap 4px 8px
|
|
30
|
+
max-width 280px
|
|
31
|
+
line-height 30px
|
|
32
|
+
.term-controls
|
|
33
|
+
float none
|
|
34
|
+
display flex
|
|
35
|
+
align-items center
|
|
36
|
+
margin-left 0
|
|
37
|
+
.sess-icon
|
|
38
|
+
margin-right 0
|
|
@@ -10,11 +10,7 @@ import VncSession from '../vnc/vnc-session'
|
|
|
10
10
|
import WebSession from '../web/web-session.jsx'
|
|
11
11
|
import SpiceSession from '../spice/spice-session'
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
FullscreenOutlined,
|
|
15
|
-
PaperClipOutlined,
|
|
16
|
-
CloseOutlined,
|
|
17
|
-
ApartmentOutlined
|
|
13
|
+
FullscreenOutlined
|
|
18
14
|
} from '@ant-design/icons'
|
|
19
15
|
import {
|
|
20
16
|
Tooltip,
|
|
@@ -25,7 +21,6 @@ import copy from 'json-deep-copy'
|
|
|
25
21
|
import classnames from 'classnames'
|
|
26
22
|
import {
|
|
27
23
|
paneMap,
|
|
28
|
-
connectionMap,
|
|
29
24
|
terminalRdpType,
|
|
30
25
|
terminalVncType,
|
|
31
26
|
terminalWebType,
|
|
@@ -33,10 +28,9 @@ import {
|
|
|
33
28
|
terminalFtpType,
|
|
34
29
|
terminalSpiceType
|
|
35
30
|
} from '../../common/constants'
|
|
36
|
-
import { SplitViewIcon } from '../icons/split-view'
|
|
37
31
|
import { refs } from '../common/ref'
|
|
38
32
|
import sanitizeFilename from '../../common/sanitize-filename.js'
|
|
39
|
-
import
|
|
33
|
+
import SessionControl from './session-control'
|
|
40
34
|
import './session.styl'
|
|
41
35
|
|
|
42
36
|
const e = window.translate
|
|
@@ -54,7 +48,8 @@ export default class SessionWrapper extends Component {
|
|
|
54
48
|
sessionOptions: null,
|
|
55
49
|
delKeyPressed: false,
|
|
56
50
|
broadcastInput: false,
|
|
57
|
-
keepaliveEnabled: false
|
|
51
|
+
keepaliveEnabled: false,
|
|
52
|
+
wrapDisabled: false
|
|
58
53
|
}
|
|
59
54
|
if (props.tab.sshSftpSplitView === undefined) {
|
|
60
55
|
props.tab.sshSftpSplitView = !!props.config.sshSftpSplitView
|
|
@@ -495,20 +490,23 @@ export default class SessionWrapper extends Component {
|
|
|
495
490
|
this.setState({ keepaliveEnabled: enabled })
|
|
496
491
|
}
|
|
497
492
|
|
|
493
|
+
handleExitGracefully = () => {
|
|
494
|
+
refs.get('term-' + this.props.tab.id)?.exitGracefully()
|
|
495
|
+
}
|
|
496
|
+
|
|
498
497
|
handleOpenSearch = () => {
|
|
499
498
|
refs.get('term-' + this.props.tab.id)?.toggleSearch()
|
|
500
499
|
}
|
|
501
500
|
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
)
|
|
501
|
+
toggleWrap = () => {
|
|
502
|
+
const termRef = refs.get('term-' + this.props.tab.id)
|
|
503
|
+
if (!termRef?.term) {
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
const next = !this.state.wrapDisabled
|
|
507
|
+
// DECAWM escape sequence: ?7l = disable wrap, ?7h = enable wrap
|
|
508
|
+
termRef.term.write(next ? '\x1b[?7l' : '\x1b[?7h')
|
|
509
|
+
this.setState({ wrapDisabled: next })
|
|
512
510
|
}
|
|
513
511
|
|
|
514
512
|
fullscreenIcon = () => {
|
|
@@ -523,237 +521,11 @@ export default class SessionWrapper extends Component {
|
|
|
523
521
|
)
|
|
524
522
|
}
|
|
525
523
|
|
|
526
|
-
renderDelTip = (isSsh) => {
|
|
527
|
-
if (!isSsh || this.props.hideDelKeyTip || !this.state.delKeyPressed) {
|
|
528
|
-
return null
|
|
529
|
-
}
|
|
530
|
-
return (
|
|
531
|
-
<div className='type-tab'>
|
|
532
|
-
<span className='mg1r'>Try <b>Shift + Backspace</b>?</span>
|
|
533
|
-
<CloseOutlined
|
|
534
|
-
onClick={this.handleDismissDelKeyTip}
|
|
535
|
-
className='pointer'
|
|
536
|
-
/>
|
|
537
|
-
</div>
|
|
538
|
-
)
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
renderKeepaliveIcon = () => {
|
|
542
|
-
if (this.isSshDisabled()) {
|
|
543
|
-
return null
|
|
544
|
-
}
|
|
545
|
-
const { keepaliveEnabled } = this.state
|
|
546
|
-
const title = e('keepalive')
|
|
547
|
-
const iconProps = {
|
|
548
|
-
className: classnames('sess-icon pointer keepalive-icon', {
|
|
549
|
-
active: keepaliveEnabled
|
|
550
|
-
}),
|
|
551
|
-
onClick: this.toggleKeepalive
|
|
552
|
-
}
|
|
553
|
-
return (
|
|
554
|
-
<Tooltip title={title}>
|
|
555
|
-
<HeartbeatIcon {...iconProps} />
|
|
556
|
-
</Tooltip>
|
|
557
|
-
)
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
renderBroadcastIcon = () => {
|
|
561
|
-
if (
|
|
562
|
-
this.isSshDisabled()
|
|
563
|
-
) {
|
|
564
|
-
return null
|
|
565
|
-
}
|
|
566
|
-
const { broadcastInput } = this.state
|
|
567
|
-
const title = e('broadcastInput')
|
|
568
|
-
const iconProps = {
|
|
569
|
-
className: classnames('sess-icon pointer broadcast-icon', {
|
|
570
|
-
active: broadcastInput
|
|
571
|
-
}),
|
|
572
|
-
onClick: this.toggleBroadcastInput
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
return (
|
|
576
|
-
<Tooltip title={title}>
|
|
577
|
-
<ApartmentOutlined {...iconProps} />
|
|
578
|
-
</Tooltip>
|
|
579
|
-
)
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
renderTermControls = () => {
|
|
583
|
-
const { props } = this
|
|
584
|
-
const { pane } = props.tab
|
|
585
|
-
if (pane !== paneMap.terminal) {
|
|
586
|
-
return null
|
|
587
|
-
}
|
|
588
|
-
return (
|
|
589
|
-
<div className='fright term-controls'>
|
|
590
|
-
{this.fullscreenIcon()}
|
|
591
|
-
{this.renderSearchIcon()}
|
|
592
|
-
</div>
|
|
593
|
-
)
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
renderSplitToggle = () => {
|
|
597
|
-
if (window.store.isMobile) {
|
|
598
|
-
return null
|
|
599
|
-
}
|
|
600
|
-
if (!this.canSplitView() || this.isNotTerminalType()) {
|
|
601
|
-
return null
|
|
602
|
-
}
|
|
603
|
-
const title = e('sshSftpSplitView')
|
|
604
|
-
const {
|
|
605
|
-
sshSftpSplitView
|
|
606
|
-
} = this.props.tab
|
|
607
|
-
const cls = classnames(
|
|
608
|
-
'pointer sess-icon split-view-toggle',
|
|
609
|
-
{
|
|
610
|
-
active: sshSftpSplitView
|
|
611
|
-
}
|
|
612
|
-
)
|
|
613
|
-
return (
|
|
614
|
-
<Tooltip title={title} placement='bottomLeft'>
|
|
615
|
-
<span
|
|
616
|
-
className={cls}
|
|
617
|
-
onClick={this.handleSshSftpSplitView}
|
|
618
|
-
>
|
|
619
|
-
<SplitViewIcon />
|
|
620
|
-
</span>
|
|
621
|
-
</Tooltip>
|
|
622
|
-
)
|
|
623
|
-
}
|
|
624
|
-
|
|
625
524
|
isSsh = () => {
|
|
626
525
|
const { tab } = this.props
|
|
627
526
|
return tab.authType
|
|
628
527
|
}
|
|
629
528
|
|
|
630
|
-
renderPaneControl = () => {
|
|
631
|
-
const {
|
|
632
|
-
sshSftpSplitView
|
|
633
|
-
} = this.props.tab
|
|
634
|
-
if (this.isDisabled()) {
|
|
635
|
-
return null
|
|
636
|
-
}
|
|
637
|
-
if (sshSftpSplitView && this.canSplitView()) {
|
|
638
|
-
return null
|
|
639
|
-
}
|
|
640
|
-
const { props } = this
|
|
641
|
-
const { tab } = props
|
|
642
|
-
const { pane } = tab
|
|
643
|
-
const termType = tab?.type
|
|
644
|
-
const isSsh = tab.authType
|
|
645
|
-
const isLocal = !isSsh && (termType === connectionMap.local || !termType)
|
|
646
|
-
const types = [
|
|
647
|
-
paneMap.terminal,
|
|
648
|
-
paneMap.fileManager
|
|
649
|
-
]
|
|
650
|
-
const controls = [
|
|
651
|
-
isSsh ? paneMap.ssh : paneMap.terminal
|
|
652
|
-
]
|
|
653
|
-
if (isSsh || isLocal) {
|
|
654
|
-
controls.push(isSsh ? paneMap.sftp : paneMap.fileManager)
|
|
655
|
-
}
|
|
656
|
-
const simpleMapper = {
|
|
657
|
-
[paneMap.terminal]: 'T',
|
|
658
|
-
[paneMap.fileManager]: 'F',
|
|
659
|
-
[paneMap.ssh]: 'T',
|
|
660
|
-
[paneMap.sftp]: 'S'
|
|
661
|
-
}
|
|
662
|
-
return (
|
|
663
|
-
<div className='term-sftp-tabs fleft'>
|
|
664
|
-
{
|
|
665
|
-
controls.map((type, i) => {
|
|
666
|
-
const cls = classnames(
|
|
667
|
-
'type-tab',
|
|
668
|
-
type,
|
|
669
|
-
{
|
|
670
|
-
active: types[i] === pane
|
|
671
|
-
}
|
|
672
|
-
)
|
|
673
|
-
return (
|
|
674
|
-
<span
|
|
675
|
-
className={cls}
|
|
676
|
-
key={type + '_' + i}
|
|
677
|
-
onClick={() => this.onChangePane(types[i])}
|
|
678
|
-
>
|
|
679
|
-
<span className='type-tab-txt'>
|
|
680
|
-
<span className='type-tab-full'>{e(type)}</span>
|
|
681
|
-
<span className='type-tab-short'>{simpleMapper[type]}</span>
|
|
682
|
-
<span className='type-tab-line' />
|
|
683
|
-
</span>
|
|
684
|
-
</span>
|
|
685
|
-
)
|
|
686
|
-
})
|
|
687
|
-
}
|
|
688
|
-
</div>
|
|
689
|
-
)
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
renderSftpPathFollowControl = () => {
|
|
693
|
-
if (this.isDisabled()) {
|
|
694
|
-
return null
|
|
695
|
-
}
|
|
696
|
-
const {
|
|
697
|
-
sftpPathFollowSsh
|
|
698
|
-
} = this.state
|
|
699
|
-
const { props } = this
|
|
700
|
-
const { tab } = props
|
|
701
|
-
const { pane, enableSsh, sshSftpSplitView } = tab
|
|
702
|
-
const termType = tab?.type
|
|
703
|
-
const isSsh = tab.authType
|
|
704
|
-
const isLocal = !isSsh && (termType === connectionMap.local || !termType)
|
|
705
|
-
const checkTxt = e('sftpPathFollowSsh')
|
|
706
|
-
const checkProps = {
|
|
707
|
-
onClick: this.toggleCheckSftpPathFollowSsh,
|
|
708
|
-
className: classnames(
|
|
709
|
-
'sftp-follow-ssh-icon sess-icon pointer',
|
|
710
|
-
{
|
|
711
|
-
active: sftpPathFollowSsh
|
|
712
|
-
}
|
|
713
|
-
)
|
|
714
|
-
}
|
|
715
|
-
const isS = pane === paneMap.terminal ||
|
|
716
|
-
sshSftpSplitView
|
|
717
|
-
return (
|
|
718
|
-
<>
|
|
719
|
-
{
|
|
720
|
-
(isSsh && enableSsh) || isLocal
|
|
721
|
-
? (
|
|
722
|
-
<Tooltip title={checkTxt}>
|
|
723
|
-
<span {...checkProps}>
|
|
724
|
-
<PaperClipOutlined />
|
|
725
|
-
</span>
|
|
726
|
-
</Tooltip>
|
|
727
|
-
)
|
|
728
|
-
: null
|
|
729
|
-
}
|
|
730
|
-
{
|
|
731
|
-
this.renderDelTip(isS)
|
|
732
|
-
}
|
|
733
|
-
</>
|
|
734
|
-
)
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
renderControl = () => {
|
|
738
|
-
if (
|
|
739
|
-
this.isNotTerminalType()
|
|
740
|
-
) {
|
|
741
|
-
return null
|
|
742
|
-
}
|
|
743
|
-
return (
|
|
744
|
-
<div
|
|
745
|
-
className='terminal-control fix'
|
|
746
|
-
>
|
|
747
|
-
{this.renderPaneControl()}
|
|
748
|
-
{this.renderSftpPathFollowControl()}
|
|
749
|
-
{this.renderSplitToggle()}
|
|
750
|
-
{this.renderKeepaliveIcon()}
|
|
751
|
-
{this.renderBroadcastIcon()}
|
|
752
|
-
{this.renderTermControls()}
|
|
753
|
-
</div>
|
|
754
|
-
)
|
|
755
|
-
}
|
|
756
|
-
|
|
757
529
|
onSplitResize = (sizes) => {
|
|
758
530
|
const direction = this.getSplitDirection()
|
|
759
531
|
const {
|
|
@@ -850,7 +622,30 @@ export default class SessionWrapper extends Component {
|
|
|
850
622
|
ref={this.domRef}
|
|
851
623
|
{...divProps}
|
|
852
624
|
>
|
|
853
|
-
|
|
625
|
+
<SessionControl
|
|
626
|
+
tab={this.props.tab}
|
|
627
|
+
isMobile={window.store.isMobile}
|
|
628
|
+
isDisabled={this.isDisabled()}
|
|
629
|
+
isSshDisabled={this.isSshDisabled()}
|
|
630
|
+
isNotTerminalType={this.isNotTerminalType()}
|
|
631
|
+
canSplitView={this.canSplitView()}
|
|
632
|
+
sftpPathFollowSsh={this.state.sftpPathFollowSsh}
|
|
633
|
+
keepaliveEnabled={this.state.keepaliveEnabled}
|
|
634
|
+
broadcastInput={this.state.broadcastInput}
|
|
635
|
+
wrapDisabled={this.state.wrapDisabled}
|
|
636
|
+
delKeyPressed={this.state.delKeyPressed}
|
|
637
|
+
hideDelKeyTip={this.props.hideDelKeyTip}
|
|
638
|
+
onChangePane={(pane) => this.onChangePane(pane)}
|
|
639
|
+
toggleCheckSftpPathFollowSsh={this.toggleCheckSftpPathFollowSsh}
|
|
640
|
+
onSshSftpSplitView={this.handleSshSftpSplitView}
|
|
641
|
+
toggleKeepalive={this.toggleKeepalive}
|
|
642
|
+
toggleBroadcastInput={this.toggleBroadcastInput}
|
|
643
|
+
toggleWrap={this.toggleWrap}
|
|
644
|
+
onFullscreen={this.handleFullscreen}
|
|
645
|
+
onOpenSearch={this.handleOpenSearch}
|
|
646
|
+
onDismissDelKeyTip={this.handleDismissDelKeyTip}
|
|
647
|
+
onExitGracefully={this.handleExitGracefully}
|
|
648
|
+
/>
|
|
854
649
|
{this.renderViews()}
|
|
855
650
|
</div>
|
|
856
651
|
)
|