@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
package/client/common/pre.js
CHANGED
|
@@ -2,7 +2,7 @@ import { formItemLayout } from '../../../common/form-layout.js'
|
|
|
2
2
|
import { terminalRdpType } from '../../../common/constants.js'
|
|
3
3
|
import { createBaseInitValues, getAuthTypeDefault } from '../common/init-values.js'
|
|
4
4
|
import { isEmpty } from 'lodash-es'
|
|
5
|
-
import { commonFields } from './common-fields.js'
|
|
5
|
+
import { commonFields, connectionHoppingTab } from './common-fields.js'
|
|
6
6
|
|
|
7
7
|
const e = window.translate
|
|
8
8
|
|
|
@@ -12,6 +12,7 @@ const rdpConfig = {
|
|
|
12
12
|
initValues: (props) => {
|
|
13
13
|
return createBaseInitValues(props, terminalRdpType, {
|
|
14
14
|
port: 3389,
|
|
15
|
+
connectionHoppings: [],
|
|
15
16
|
...getAuthTypeDefault(props)
|
|
16
17
|
})
|
|
17
18
|
},
|
|
@@ -38,7 +39,8 @@ const rdpConfig = {
|
|
|
38
39
|
commonFields.proxy,
|
|
39
40
|
commonFields.type
|
|
40
41
|
]
|
|
41
|
-
}
|
|
42
|
+
},
|
|
43
|
+
connectionHoppingTab()
|
|
42
44
|
]
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Icon from '@ant-design/icons'
|
|
2
|
+
|
|
3
|
+
const HeartbeatSvg = () => (
|
|
4
|
+
<svg
|
|
5
|
+
viewBox='0 0 1024 1024'
|
|
6
|
+
fill='currentColor'
|
|
7
|
+
width='1em'
|
|
8
|
+
height='1em'
|
|
9
|
+
aria-hidden='true'
|
|
10
|
+
>
|
|
11
|
+
<path d='M923 283.6a260.1 260.1 0 00-56.9-82.8 264.4 264.4 0 00-84-55.5A265.3 265.3 0 00679.7 128c-38.1 0-75.4 7.5-109.8 22.3a274.5 274.5 0 00-87.7 60.8l-12.1 12.5-12.1-12.5a260.5 260.5 0 00-87.7-60.8c-34.4-14.8-71.7-22.3-109.8-22.3-38.2 0-75.5 7.5-109.9 22.3-33.4 14.3-63.3 34.9-88.9 61-25.6 26.1-45.7 56.4-59.9 90.5A278.3 278.3 0 000 416.5c0 39.6 7.7 77.2 22.9 111.6 12.8 29.6 31.5 57 55.5 81.5l356.2 358.5c12.2 12.3 29.7 19.4 47.8 19.4 18.1 0 35.6-7.1 47.7-19.4L886 609.6c24-24.5 42.7-51.9 55.5-81.5C956.3 493.7 964 456.1 964 416.5c0-37.9-7.4-74.7-22-109zM880 497.9c-10.4 24.1-26 46-46.5 65.2L480 920.7 193.5 563.1C173 543.9 157.4 522 147 497.9c-12.1-27.9-18.2-57.8-18.2-88.6 0-30 5.8-59 17.3-86.3 11.1-26.5 27.2-50.3 47.8-70.8 20.6-20.4 44.6-36.5 71.4-47.8 27.7-11.7 57.2-17.7 87.8-17.7 32.3 0 63.7 6.5 93.2 19.3 29 12.5 55 30.9 77.2 54.4l73.9 76.5 73.9-76.5c22.2-23.5 48.2-41.9 77.2-54.4 29.5-12.8 60.9-19.3 93.2-19.3 30.6 0 60.1 6 87.8 17.7 26.8 11.3 50.8 27.4 71.4 47.8 20.6 20.5 36.7 44.3 47.8 70.8 11.5 27.3 17.3 56.3 17.3 86.3 0 30.8-6.1 60.7-18.2 88.6z' />
|
|
12
|
+
<polyline
|
|
13
|
+
points='160,512 310,512 370,310 450,714 530,512 594,512 654,360 714,664 774,512 864,512'
|
|
14
|
+
fill='none'
|
|
15
|
+
stroke='currentColor'
|
|
16
|
+
strokeWidth='60'
|
|
17
|
+
strokeLinecap='round'
|
|
18
|
+
strokeLinejoin='round'
|
|
19
|
+
/>
|
|
20
|
+
</svg>
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
export const HeartbeatIcon = props => (<Icon component={HeartbeatSvg} {...props} />)
|
|
@@ -36,6 +36,7 @@ import WorkspaceSaveModal from '../tabs/workspace-save-modal'
|
|
|
36
36
|
import BookmarkFromHistoryModal from '../bookmark-form/bookmark-from-history-modal'
|
|
37
37
|
import AutoSync from '../setting-sync/auto-sync'
|
|
38
38
|
import BatchOpRunner from '../batch-op/batch-op-runner'
|
|
39
|
+
import UnixTimestampTooltip from '../terminal/unix-timestamp-tooltip'
|
|
39
40
|
import { pick } from 'lodash-es'
|
|
40
41
|
import deepCopy from 'json-deep-copy'
|
|
41
42
|
import './wrapper.styl'
|
|
@@ -51,7 +52,7 @@ export default auto(function Index (props) {
|
|
|
51
52
|
ipcOnEvent('open-about', store.openAbout)
|
|
52
53
|
ipcOnEvent('new-ssh', store.onNewSsh)
|
|
53
54
|
ipcOnEvent('add-tab-from-command-line', store.addTabFromCommandLine)
|
|
54
|
-
ipcOnEvent('open-tab', (e, parsed) => store.
|
|
55
|
+
ipcOnEvent('open-tab', (e, parsed) => store.ipcOpenTab(parsed))
|
|
55
56
|
ipcOnEvent('openSettings', store.openSetting)
|
|
56
57
|
ipcOnEvent('selectall', store.selectall)
|
|
57
58
|
ipcOnEvent('focused', store.focus)
|
|
@@ -297,6 +298,7 @@ export default auto(function Index (props) {
|
|
|
297
298
|
<NotificationContainer />
|
|
298
299
|
<BatchOpRunner />
|
|
299
300
|
<AIConfigModal store={store} />
|
|
301
|
+
<UnixTimestampTooltip />
|
|
300
302
|
</div>
|
|
301
303
|
</ConfigProvider>
|
|
302
304
|
)
|
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
FullscreenOutlined,
|
|
15
15
|
PaperClipOutlined,
|
|
16
16
|
CloseOutlined,
|
|
17
|
-
ApartmentOutlined
|
|
18
|
-
HeartOutlined
|
|
17
|
+
ApartmentOutlined
|
|
19
18
|
} from '@ant-design/icons'
|
|
20
19
|
import {
|
|
21
20
|
Tooltip,
|
|
@@ -37,6 +36,7 @@ import {
|
|
|
37
36
|
import { SplitViewIcon } from '../icons/split-view'
|
|
38
37
|
import { refs } from '../common/ref'
|
|
39
38
|
import safeName from '../../common/safe-name'
|
|
39
|
+
import { HeartbeatIcon } from '../icons/heartbeat'
|
|
40
40
|
import './session.styl'
|
|
41
41
|
|
|
42
42
|
const e = window.translate
|
|
@@ -550,7 +550,7 @@ export default class SessionWrapper extends Component {
|
|
|
550
550
|
}
|
|
551
551
|
return (
|
|
552
552
|
<Tooltip title={title}>
|
|
553
|
-
<
|
|
553
|
+
<HeartbeatIcon {...iconProps} />
|
|
554
554
|
</Tooltip>
|
|
555
555
|
)
|
|
556
556
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { useEffect, useRef } from 'react'
|
|
9
9
|
import { ArrowDownOutlined, ArrowUpOutlined, SaveOutlined, ClearOutlined } from '@ant-design/icons'
|
|
10
|
-
import { Button, Input, Form, Alert } from 'antd'
|
|
10
|
+
import { Button, Input, Form, Alert, Switch } from 'antd'
|
|
11
11
|
import { notification } from '../common/notification'
|
|
12
12
|
import Link from '../common/external-link'
|
|
13
13
|
import dayjs from 'dayjs'
|
|
@@ -80,6 +80,7 @@ export default function SyncForm (props) {
|
|
|
80
80
|
up[syncType + 'ServerUrl'] = res.serverUrl || ''
|
|
81
81
|
up[syncType + 'Username'] = res.username || ''
|
|
82
82
|
up[syncType + 'Password'] = res.password || ''
|
|
83
|
+
up[syncType + 'SkipVerify'] = res.skipVerify || false
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
window.store.updateSyncSetting(up)
|
|
@@ -247,6 +248,13 @@ export default function SyncForm (props) {
|
|
|
247
248
|
id='sync-input-webdav-username'
|
|
248
249
|
/>
|
|
249
250
|
</FormItem>
|
|
251
|
+
<FormItem
|
|
252
|
+
label={createLabel('Skip SSL verify')}
|
|
253
|
+
name='skipVerify'
|
|
254
|
+
valuePropName='checked'
|
|
255
|
+
>
|
|
256
|
+
<Switch />
|
|
257
|
+
</FormItem>
|
|
250
258
|
<FormItem
|
|
251
259
|
label={createLabel(e('password'))}
|
|
252
260
|
name='password'
|
|
@@ -48,7 +48,8 @@ export default auto(function SyncSettingEntry (props) {
|
|
|
48
48
|
// WebDAV specific fields
|
|
49
49
|
serverUrl: syncSetting[type + 'ServerUrl'],
|
|
50
50
|
username: syncSetting[type + 'Username'],
|
|
51
|
-
password: syncSetting[type + 'Password']
|
|
51
|
+
password: syncSetting[type + 'Password'],
|
|
52
|
+
skipVerify: syncSetting[type + 'SkipVerify'] || false
|
|
52
53
|
}
|
|
53
54
|
return (
|
|
54
55
|
<SyncForm
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* file list table
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { Component } from 'react'
|
|
6
|
-
import { Dropdown
|
|
5
|
+
import { Component, createRef } from 'react'
|
|
6
|
+
import { Dropdown } from 'antd'
|
|
7
7
|
import classnames from 'classnames'
|
|
8
8
|
import FileSection from './file-item'
|
|
9
9
|
import PagedList from './paged-list'
|
|
@@ -19,7 +19,29 @@ const e = window.translate
|
|
|
19
19
|
export default class FileListTable extends Component {
|
|
20
20
|
constructor (props) {
|
|
21
21
|
super(props)
|
|
22
|
-
this.state =
|
|
22
|
+
this.state = {
|
|
23
|
+
...this.initFromProps(),
|
|
24
|
+
scrollTop: 0
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
containerRef = createRef()
|
|
29
|
+
|
|
30
|
+
componentDidUpdate (prevProps) {
|
|
31
|
+
const prevList = prevProps.fileList
|
|
32
|
+
const nextList = this.props.fileList
|
|
33
|
+
const contentChanged = prevList.length !== nextList.length ||
|
|
34
|
+
prevList.some((f, i) => f.id !== nextList[i].id)
|
|
35
|
+
if (contentChanged) {
|
|
36
|
+
if (this.containerRef.current) {
|
|
37
|
+
this.containerRef.current.scrollTop = 0
|
|
38
|
+
}
|
|
39
|
+
this.setState({ scrollTop: 0 })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onScroll = (e) => {
|
|
44
|
+
this.setState({ scrollTop: e.target.scrollTop })
|
|
23
45
|
}
|
|
24
46
|
|
|
25
47
|
initFromProps = (pps = this.getPropsDefault()) => {
|
|
@@ -36,9 +58,7 @@ export default class FileListTable extends Component {
|
|
|
36
58
|
}
|
|
37
59
|
})
|
|
38
60
|
return {
|
|
39
|
-
|
|
40
|
-
properties,
|
|
41
|
-
page: 1
|
|
61
|
+
properties
|
|
42
62
|
}
|
|
43
63
|
}
|
|
44
64
|
|
|
@@ -171,40 +191,6 @@ export default class FileListTable extends Component {
|
|
|
171
191
|
'left'
|
|
172
192
|
]
|
|
173
193
|
|
|
174
|
-
hasPager = () => {
|
|
175
|
-
const {
|
|
176
|
-
pageSize
|
|
177
|
-
} = this.state
|
|
178
|
-
const {
|
|
179
|
-
fileList
|
|
180
|
-
} = this.props
|
|
181
|
-
const len = fileList.length
|
|
182
|
-
return len > pageSize
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
onPageChange = (page) => {
|
|
186
|
-
this.setState({ page })
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
renderPager () {
|
|
190
|
-
const { page, pageSize } = this.state
|
|
191
|
-
const { fileList } = this.props
|
|
192
|
-
const props = {
|
|
193
|
-
current: page,
|
|
194
|
-
pageSize,
|
|
195
|
-
total: fileList.length,
|
|
196
|
-
showLessItems: true,
|
|
197
|
-
showSizeChanger: false,
|
|
198
|
-
simple: false,
|
|
199
|
-
onChange: this.onPageChange
|
|
200
|
-
}
|
|
201
|
-
return (
|
|
202
|
-
<div className='pd1b pager-wrap'>
|
|
203
|
-
<Pagination {...props} />
|
|
204
|
-
</div>
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
194
|
// reset
|
|
209
195
|
resetWidth = () => {
|
|
210
196
|
this.setState(this.initFromProps())
|
|
@@ -293,24 +279,19 @@ export default class FileListTable extends Component {
|
|
|
293
279
|
|
|
294
280
|
render () {
|
|
295
281
|
const { fileList, height, type } = this.props
|
|
296
|
-
|
|
297
|
-
// const sh = sshSftpSplitView ? 0 : 32
|
|
282
|
+
const containerHeight = height - 42 - 30 - 32 - 90
|
|
298
283
|
const props = {
|
|
284
|
+
ref: this.containerRef,
|
|
299
285
|
className: 'sftp-table-content overscroll-y relative',
|
|
300
286
|
style: {
|
|
301
|
-
height:
|
|
287
|
+
height: containerHeight
|
|
302
288
|
},
|
|
303
289
|
draggable: false,
|
|
290
|
+
onScroll: this.onScroll,
|
|
304
291
|
onClick: this.handleClick,
|
|
305
292
|
onDoubleClick: this.handleDoubleClick
|
|
306
293
|
}
|
|
307
|
-
const
|
|
308
|
-
const cls = classnames(
|
|
309
|
-
'sftp-table relative',
|
|
310
|
-
{
|
|
311
|
-
'sftp-has-pager': hasPager
|
|
312
|
-
}
|
|
313
|
-
)
|
|
294
|
+
const cls = classnames('sftp-table relative')
|
|
314
295
|
const ddProps = {
|
|
315
296
|
menu: {
|
|
316
297
|
items: this.renderContextMenuFile(),
|
|
@@ -338,13 +319,11 @@ export default class FileListTable extends Component {
|
|
|
338
319
|
<PagedList
|
|
339
320
|
list={fileList}
|
|
340
321
|
renderItem={this.renderItem}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
pageSize={this.state.pageSize}
|
|
322
|
+
scrollTop={this.state.scrollTop}
|
|
323
|
+
containerHeight={containerHeight}
|
|
344
324
|
/>
|
|
345
325
|
</div>
|
|
346
326
|
</Dropdown>
|
|
347
|
-
{hasPager && this.renderPager()}
|
|
348
327
|
</div>
|
|
349
328
|
)
|
|
350
329
|
}
|
|
@@ -1,56 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Virtual list for SFTP file list.
|
|
3
|
+
*
|
|
4
|
+
* Scroll state is owned by the parent (list-table-ui) via React's onScroll prop
|
|
5
|
+
* on the scrollable container, and passed down as `scrollTop`. This avoids the
|
|
6
|
+
* React lifecycle ordering bug where a child's componentDidMount fires before the
|
|
7
|
+
* parent div's ref is assigned, making addEventListener attach to null.
|
|
8
|
+
*
|
|
9
|
+
* Uses spacers (top/bottom divs) so the container's scrollbar reflects the full
|
|
10
|
+
* list height while only the visible window (± OVERSCAN) is in the DOM.
|
|
11
|
+
*
|
|
12
|
+
* offsetTop is read from rootRef.offsetTop at render time — the distance from the
|
|
13
|
+
* scroll container's top edge to this list's top edge (accounts for the ".." parent
|
|
14
|
+
* row above the list).
|
|
3
15
|
*/
|
|
4
16
|
|
|
5
|
-
import { Component } from 'react'
|
|
6
|
-
import { Pagination } from 'antd'
|
|
17
|
+
import { Component, createRef } from 'react'
|
|
7
18
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
page: 1,
|
|
11
|
-
pageSize: 100
|
|
12
|
-
}
|
|
19
|
+
const ITEM_SIZE = 36 // 32px item height + 4px margin-bottom
|
|
20
|
+
const OVERSCAN = 5
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
page
|
|
17
|
-
})
|
|
18
|
-
}
|
|
22
|
+
export default class VirtualList extends Component {
|
|
23
|
+
rootRef = createRef()
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
render () {
|
|
26
|
+
const { list, renderItem, containerHeight = 400, scrollTop = 0 } = this.props
|
|
27
|
+
|
|
28
|
+
// offsetTop: distance from scroll container top to this list's top.
|
|
29
|
+
// rootRef.offsetTop is relative to the nearest positioned ancestor, which is
|
|
30
|
+
// .sftp-table-content (position: relative) — exactly the scroll container.
|
|
31
|
+
// Returns 0 on first render (rootRef not yet set); harmless (renders a few extra items).
|
|
32
|
+
const offsetTop = this.rootRef.current?.offsetTop ?? 0
|
|
33
|
+
|
|
34
|
+
const startIndex = Math.max(
|
|
35
|
+
0,
|
|
36
|
+
Math.floor((scrollTop - offsetTop) / ITEM_SIZE) - OVERSCAN
|
|
37
|
+
)
|
|
38
|
+
const endIndex = Math.min(
|
|
39
|
+
list.length - 1,
|
|
40
|
+
Math.ceil((scrollTop + containerHeight - offsetTop) / ITEM_SIZE) + OVERSCAN
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const topSpacerHeight = startIndex * ITEM_SIZE
|
|
44
|
+
const bottomSpacerHeight = Math.max(0, (list.length - endIndex - 1) * ITEM_SIZE)
|
|
33
45
|
|
|
34
|
-
renderPager () {
|
|
35
|
-
const props = {
|
|
36
|
-
current: this.state.page,
|
|
37
|
-
pageSize: this.state.pageSize,
|
|
38
|
-
total: this.props.list.length,
|
|
39
|
-
showLessItems: true,
|
|
40
|
-
showSizeChanger: false,
|
|
41
|
-
simple: false,
|
|
42
|
-
onChange: this.onChange
|
|
43
|
-
}
|
|
44
46
|
return (
|
|
45
|
-
<div
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
<div ref={this.rootRef}>
|
|
48
|
+
<div style={{ height: topSpacerHeight }} />
|
|
49
|
+
{list.slice(startIndex, endIndex + 1).map((item, i) =>
|
|
50
|
+
renderItem(item, startIndex + i)
|
|
51
|
+
)}
|
|
52
|
+
<div style={{ height: bottomSpacerHeight }} />
|
|
49
53
|
</div>
|
|
50
54
|
)
|
|
51
55
|
}
|
|
52
|
-
|
|
53
|
-
render () {
|
|
54
|
-
return this.renderList()
|
|
55
|
-
}
|
|
56
56
|
}
|
|
@@ -970,7 +970,7 @@ export default class Sftp extends Component {
|
|
|
970
970
|
})
|
|
971
971
|
}
|
|
972
972
|
|
|
973
|
-
parsePath = (type, pth) => {
|
|
973
|
+
parsePath = async (type, pth) => {
|
|
974
974
|
const reg = /^%([^%]+)%/
|
|
975
975
|
if (!reg.test(pth)) {
|
|
976
976
|
return pth
|
|
@@ -980,13 +980,14 @@ export default class Sftp extends Component {
|
|
|
980
980
|
return pth
|
|
981
981
|
}
|
|
982
982
|
const envName = m[1]
|
|
983
|
-
const envPath = window.pre.
|
|
983
|
+
const envPath = await window.pre.runGlobalAsync('getEnv', envName)
|
|
984
984
|
if (envPath) {
|
|
985
985
|
return pth.replace(reg, envPath)
|
|
986
986
|
}
|
|
987
|
+
return pth
|
|
987
988
|
}
|
|
988
989
|
|
|
989
|
-
onGoto = (type, e) => {
|
|
990
|
+
onGoto = async (type, e) => {
|
|
990
991
|
e && e.preventDefault()
|
|
991
992
|
if (type === typeMap.remote && !this.sftp) {
|
|
992
993
|
return this.initData(true)
|
|
@@ -994,7 +995,7 @@ export default class Sftp extends Component {
|
|
|
994
995
|
const n = `${type}Path`
|
|
995
996
|
const nt = n + 'Temp'
|
|
996
997
|
const oldPath = this.state[type + 'Path']
|
|
997
|
-
const np = this.parsePath(type, this.state[nt])
|
|
998
|
+
const np = await this.parsePath(type, this.state[nt])
|
|
998
999
|
if (!isValidPath(np)) {
|
|
999
1000
|
return notification.warning({
|
|
1000
1001
|
message: 'path not valid'
|
|
@@ -16,6 +16,7 @@ import Link from '../common/external-link'
|
|
|
16
16
|
import LogoElem from '../common/logo-elem'
|
|
17
17
|
import RunningTime from './app-running-time'
|
|
18
18
|
import { auto } from 'manate/react'
|
|
19
|
+
import { useState } from 'react'
|
|
19
20
|
|
|
20
21
|
import {
|
|
21
22
|
packInfo,
|
|
@@ -27,8 +28,13 @@ import './info.styl'
|
|
|
27
28
|
const e = window.translate
|
|
28
29
|
|
|
29
30
|
export default auto(function InfoModal (props) {
|
|
31
|
+
const [runtimeEnv, setRuntimeEnv] = useState(null)
|
|
32
|
+
|
|
30
33
|
const handleChangeTab = key => {
|
|
31
34
|
window.store.infoModalTab = key
|
|
35
|
+
if (key === infoTabs.env && !runtimeEnv) {
|
|
36
|
+
window.pre.runGlobalAsync('getEnv').then(env => setRuntimeEnv(env))
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
const renderCheckUpdate = () => {
|
|
@@ -135,14 +141,14 @@ export default auto(function InfoModal (props) {
|
|
|
135
141
|
knownIssuesLink
|
|
136
142
|
} = packInfo
|
|
137
143
|
const link = releaseLink.replace('/releases', '')
|
|
138
|
-
const {
|
|
144
|
+
const { versions } = window.pre
|
|
139
145
|
const deps = {
|
|
140
146
|
...devDependencies,
|
|
141
147
|
...dependencies
|
|
142
148
|
}
|
|
143
149
|
const envs = {
|
|
144
150
|
...versions,
|
|
145
|
-
...
|
|
151
|
+
...(runtimeEnv || {})
|
|
146
152
|
}
|
|
147
153
|
const title = (
|
|
148
154
|
<div className='custom-modal-close-confirm-title font16'>
|
|
@@ -8,7 +8,8 @@ import { refsTabs } from '../common/ref'
|
|
|
8
8
|
import {
|
|
9
9
|
CloseOutlined,
|
|
10
10
|
Loading3QuartersOutlined,
|
|
11
|
-
BorderlessTableOutlined
|
|
11
|
+
BorderlessTableOutlined,
|
|
12
|
+
LockOutlined
|
|
12
13
|
} from '@ant-design/icons'
|
|
13
14
|
import {
|
|
14
15
|
Tooltip,
|
|
@@ -33,7 +34,7 @@ class Tab extends Component {
|
|
|
33
34
|
constructor (props) {
|
|
34
35
|
super(props)
|
|
35
36
|
this.state = {
|
|
36
|
-
terminalOnData:
|
|
37
|
+
terminalOnData: ''
|
|
37
38
|
}
|
|
38
39
|
this.id = 'tab-' + this.props.tab.id
|
|
39
40
|
refsTabs.add(this.id, this)
|
|
@@ -48,19 +49,38 @@ class Tab extends Component {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
notifyOnData = () => {
|
|
52
|
+
if (this.state.terminalOnData === 'password') {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
51
55
|
if (this.timer) {
|
|
52
56
|
clearTimeout(this.timer)
|
|
53
57
|
this.timer = null
|
|
54
58
|
}
|
|
55
59
|
this.setState({
|
|
56
|
-
terminalOnData:
|
|
60
|
+
terminalOnData: 'feed'
|
|
57
61
|
})
|
|
58
62
|
this.timer = setTimeout(this.clearTerminalOnData, 4000)
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
clearTerminalOnData = () => {
|
|
62
66
|
this.setState({
|
|
63
|
-
terminalOnData:
|
|
67
|
+
terminalOnData: ''
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
notifyPasswordPrompt = () => {
|
|
72
|
+
if (this.timer) {
|
|
73
|
+
clearTimeout(this.timer)
|
|
74
|
+
this.timer = null
|
|
75
|
+
}
|
|
76
|
+
this.setState({
|
|
77
|
+
terminalOnData: 'password'
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
clearPasswordPrompt = () => {
|
|
82
|
+
this.setState({
|
|
83
|
+
terminalOnData: ''
|
|
64
84
|
})
|
|
65
85
|
}
|
|
66
86
|
|
|
@@ -424,13 +444,7 @@ class Tab extends Component {
|
|
|
424
444
|
{
|
|
425
445
|
'tab-last': isLast
|
|
426
446
|
},
|
|
427
|
-
status
|
|
428
|
-
{
|
|
429
|
-
'is-terminal-active': terminalOnData
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
'is-transporting': isTransporting
|
|
433
|
-
}
|
|
447
|
+
status
|
|
434
448
|
)
|
|
435
449
|
const title = createName(tab)
|
|
436
450
|
let tooltipTitle = title
|
|
@@ -477,15 +491,19 @@ class Tab extends Component {
|
|
|
477
491
|
>
|
|
478
492
|
<Dropdown {...dropdownProps}>
|
|
479
493
|
<div
|
|
480
|
-
className='tab-title elli
|
|
494
|
+
className='tab-title elli'
|
|
481
495
|
onClick={this.handleClick}
|
|
482
496
|
onDoubleClick={this.handleDup}
|
|
483
497
|
>
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
498
|
+
{
|
|
499
|
+
status === 'error' && (
|
|
500
|
+
<Loading3QuartersOutlined
|
|
501
|
+
className='pointer tab-reload mg1r'
|
|
502
|
+
onClick={this.handleReloadTab}
|
|
503
|
+
title={e('reload')}
|
|
504
|
+
/>
|
|
505
|
+
)
|
|
506
|
+
}
|
|
489
507
|
<span className='tab-title'>
|
|
490
508
|
<span className='iblock mg1r tab-count' style={styleTag}>{tabCount}</span>
|
|
491
509
|
<span className='mg1r'>{title}</span>
|
|
@@ -493,8 +511,9 @@ class Tab extends Component {
|
|
|
493
511
|
</div>
|
|
494
512
|
</Dropdown>
|
|
495
513
|
<div className={'tab-status ' + status} />
|
|
496
|
-
<div className='tab-traffic' />
|
|
497
|
-
<BorderlessTableOutlined className='tab-terminal-feed' />
|
|
514
|
+
{isTransporting && <div className='tab-traffic' />}
|
|
515
|
+
{terminalOnData === 'feed' && <BorderlessTableOutlined className='tab-terminal-feed' />}
|
|
516
|
+
{terminalOnData === 'password' && <LockOutlined className='tab-terminal-feed password' />}
|
|
498
517
|
{
|
|
499
518
|
this.renderCloseIcon()
|
|
500
519
|
}
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
vertical-align middle
|
|
37
37
|
cursor pointer
|
|
38
38
|
position relative
|
|
39
|
+
padding 0 14px
|
|
39
40
|
min-width 100px
|
|
40
41
|
max-width 200px
|
|
41
42
|
line-height 36px
|
|
@@ -46,7 +47,6 @@
|
|
|
46
47
|
color var(--text-dark)
|
|
47
48
|
&.tab-last
|
|
48
49
|
margin-right 5px
|
|
49
|
-
.tab-reload
|
|
50
50
|
.tab-close
|
|
51
51
|
display none
|
|
52
52
|
&.active
|
|
@@ -68,10 +68,6 @@
|
|
|
68
68
|
width 1px
|
|
69
69
|
border 1px dashed var(--text-dark)
|
|
70
70
|
height 36px
|
|
71
|
-
&.error
|
|
72
|
-
.tab-reload
|
|
73
|
-
display inline-block
|
|
74
|
-
color var(--text-light)
|
|
75
71
|
@keyframes blink
|
|
76
72
|
0%
|
|
77
73
|
background-color #e0e0e0
|
|
@@ -96,30 +92,25 @@
|
|
|
96
92
|
background-color var(--error)
|
|
97
93
|
&.processing
|
|
98
94
|
background-color var(--primary)
|
|
99
|
-
.is-transporting .tab-traffic
|
|
100
|
-
display block
|
|
101
|
-
animation blink 2s infinite
|
|
102
|
-
/* Remove opacity animation, use background-color */
|
|
103
95
|
.tab-traffic
|
|
104
|
-
display none
|
|
105
96
|
left 10px
|
|
106
97
|
width 5px
|
|
107
98
|
border-radius 0
|
|
108
99
|
background-color var(--success)
|
|
109
|
-
.is-terminal-active .tab-terminal-feed
|
|
110
|
-
display block
|
|
111
100
|
animation blink 2s infinite
|
|
112
|
-
background-color transparent !important
|
|
113
|
-
/* Remove opacity animation, use background-color */
|
|
114
101
|
.tab-terminal-feed
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
width 12px
|
|
103
|
+
height 12px
|
|
117
104
|
border-radius 0
|
|
118
105
|
color var(--success)
|
|
119
|
-
font-size
|
|
106
|
+
font-size 12px
|
|
120
107
|
left 2px
|
|
121
108
|
top 24px
|
|
122
109
|
background none
|
|
110
|
+
background-color transparent !important
|
|
111
|
+
animation blink 2s infinite
|
|
112
|
+
&.password
|
|
113
|
+
color var(--warn)
|
|
123
114
|
.tab-close
|
|
124
115
|
position absolute
|
|
125
116
|
right 5px
|
|
@@ -223,6 +223,7 @@ export default class AttachAddonCustom {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
if (typeof data === 'string') {
|
|
226
|
+
term?.parent?.notifyOnData()
|
|
226
227
|
return term.write(data)
|
|
227
228
|
}
|
|
228
229
|
data = new Uint8Array(data)
|
|
@@ -242,13 +243,16 @@ export default class AttachAddonCustom {
|
|
|
242
243
|
sendToServer = (data) => {
|
|
243
244
|
this._lastInputTime = Date.now()
|
|
244
245
|
// Start echo detection when password prompt is suspected
|
|
245
|
-
if (this._passwordPromptDetected && !this._pendingEchoCheck && data !== '\r' && data !== '\n') {
|
|
246
|
+
if (this._passwordPromptDetected && !this._pendingEchoCheck && data !== '\r' && data !== '\n' && data !== '\x03') {
|
|
246
247
|
this._pendingEchoCheck = { char: data, time: Date.now() }
|
|
247
248
|
clearTimeout(this._echoCheckTimer)
|
|
248
249
|
this._echoCheckTimer = setTimeout(this._onEchoCheckTimeout, 200)
|
|
249
250
|
}
|
|
250
|
-
// Reset password state on Enter
|
|
251
|
-
if (data === '\r' || data === '\n') {
|
|
251
|
+
// Reset password state on Enter or Ctrl+C
|
|
252
|
+
if (data === '\r' || data === '\n' || data === '\x03') {
|
|
253
|
+
if (this._passwordPromptDetected) {
|
|
254
|
+
this.term?.parent?.onPasswordPromptCancelled?.()
|
|
255
|
+
}
|
|
252
256
|
this._passwordPromptDetected = false
|
|
253
257
|
this._lastOutputLine = ''
|
|
254
258
|
this._pendingEchoCheck = null
|