@electerm/electerm-react 3.0.6 → 3.0.18
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/default-setting.js +1 -0
- package/client/components/bookmark-form/config/ftp.js +1 -0
- package/client/components/session/sessions.jsx +1 -0
- package/client/components/setting-panel/setting-terminal.jsx +2 -1
- package/client/components/sftp/address-bookmark-item.jsx +1 -1
- package/client/components/sftp/address-bookmark.jsx +11 -1
- package/client/components/sftp/file-item.jsx +1 -1
- package/client/components/tabs/tabs.styl +1 -1
- package/client/components/terminal/reconnect-overlay.jsx +27 -0
- package/client/components/terminal/socket-close-warning.jsx +94 -0
- package/client/components/terminal/terminal.jsx +80 -46
- package/client/components/terminal/terminal.styl +12 -0
- package/client/store/tab.js +2 -1
- package/package.json +1 -1
|
@@ -589,7 +589,8 @@ export default class SettingTerminal extends Component {
|
|
|
589
589
|
'ctrlOrMetaOpenTerminalLink',
|
|
590
590
|
'sftpPathFollowSsh',
|
|
591
591
|
'sshSftpSplitView',
|
|
592
|
-
'showCmdSuggestions'
|
|
592
|
+
'showCmdSuggestions',
|
|
593
|
+
'autoReconnectTerminal'
|
|
593
594
|
].map(d => this.renderToggle(d))
|
|
594
595
|
}
|
|
595
596
|
<div className='pd1b'>{e('terminalBackSpaceMode')}</div>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
1
2
|
import { auto } from 'manate/react'
|
|
2
3
|
import {
|
|
3
4
|
StarOutlined,
|
|
@@ -13,6 +14,8 @@ import uid from '../../common/uid'
|
|
|
13
14
|
import './address-bookmark.styl'
|
|
14
15
|
|
|
15
16
|
export default auto(function AddrBookmark (props) {
|
|
17
|
+
const [open, setOpen] = useState(false)
|
|
18
|
+
|
|
16
19
|
function onDel (item) {
|
|
17
20
|
window.store.delAddressBookmark(item)
|
|
18
21
|
}
|
|
@@ -39,6 +42,11 @@ export default auto(function AddrBookmark (props) {
|
|
|
39
42
|
handleAddAddrAct(true)
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
function handleClick (type, addr) {
|
|
46
|
+
setOpen(false)
|
|
47
|
+
onClickHistory(type, addr)
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
const { type, onClickHistory, host } = props
|
|
43
51
|
const { store } = window
|
|
44
52
|
// const cls = classnames(
|
|
@@ -58,7 +66,7 @@ export default auto(function AddrBookmark (props) {
|
|
|
58
66
|
? addrs.map(o => {
|
|
59
67
|
return (
|
|
60
68
|
<AddrBookmarkItem
|
|
61
|
-
handleClick={
|
|
69
|
+
handleClick={handleClick}
|
|
62
70
|
type={type}
|
|
63
71
|
key={o.id}
|
|
64
72
|
handleDel={onDel}
|
|
@@ -100,6 +108,8 @@ export default auto(function AddrBookmark (props) {
|
|
|
100
108
|
title={title}
|
|
101
109
|
placement='bottom'
|
|
102
110
|
trigger='click'
|
|
111
|
+
open={open}
|
|
112
|
+
onOpenChange={setOpen}
|
|
103
113
|
>
|
|
104
114
|
<StarOutlined className={props.className || ''} />
|
|
105
115
|
</Popover>
|
|
@@ -998,7 +998,7 @@ export default class FileSection extends React.Component {
|
|
|
998
998
|
const shouldShowSelectedMenu = id &&
|
|
999
999
|
len > 1 &&
|
|
1000
1000
|
selectedFiles.has(id)
|
|
1001
|
-
const delTxt = shouldShowSelectedMenu ? `${e('
|
|
1001
|
+
const delTxt = shouldShowSelectedMenu ? `${e('del')}:${e('selected')}(${len})` : e('del')
|
|
1002
1002
|
const canPaste = hasFileInClipboardText()
|
|
1003
1003
|
const showEdit = !isDirectory && id &&
|
|
1004
1004
|
size < maxEditFileSize
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { memo } from 'react'
|
|
2
|
+
import { Button } from 'antd'
|
|
3
|
+
import { LoadingOutlined } from '@ant-design/icons'
|
|
4
|
+
|
|
5
|
+
const e = window.translate
|
|
6
|
+
|
|
7
|
+
export default memo(function ReconnectOverlay ({ countdown, onCancel }) {
|
|
8
|
+
if (countdown === null || countdown === undefined) {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
return (
|
|
12
|
+
<div className='terminal-reconnect-overlay'>
|
|
13
|
+
<div className='terminal-reconnect-box'>
|
|
14
|
+
<LoadingOutlined className='terminal-reconnect-icon' />
|
|
15
|
+
<div className='terminal-reconnect-msg'>
|
|
16
|
+
{e('autoReconnectTerminal')}: {countdown}s
|
|
17
|
+
</div>
|
|
18
|
+
<Button
|
|
19
|
+
size='small'
|
|
20
|
+
onClick={onCancel}
|
|
21
|
+
>
|
|
22
|
+
{e('cancel')}
|
|
23
|
+
</Button>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
}
|
|
@@ -3,12 +3,8 @@ import { handleErr } from '../../common/fetch.jsx'
|
|
|
3
3
|
import { isEqual, pick, debounce, throttle } from 'lodash-es'
|
|
4
4
|
import clone from '../../common/to-simple-obj.js'
|
|
5
5
|
import resolve from '../../common/resolve.js'
|
|
6
|
-
import {
|
|
7
|
-
ReloadOutlined
|
|
8
|
-
} from '@ant-design/icons'
|
|
9
6
|
import {
|
|
10
7
|
Spin,
|
|
11
|
-
Button,
|
|
12
8
|
Dropdown
|
|
13
9
|
} from 'antd'
|
|
14
10
|
import { notification } from '../common/notification'
|
|
@@ -50,6 +46,8 @@ import ExternalLink from '../common/external-link.jsx'
|
|
|
50
46
|
import createDefaultLogPath from '../../common/default-log-path.js'
|
|
51
47
|
import SearchResultBar from './terminal-search-bar'
|
|
52
48
|
import RemoteFloatControl from '../common/remote-float-control'
|
|
49
|
+
import { showSocketCloseWarning } from './socket-close-warning.jsx'
|
|
50
|
+
import ReconnectOverlay from './reconnect-overlay.jsx'
|
|
53
51
|
import {
|
|
54
52
|
loadTerminal,
|
|
55
53
|
loadFitAddon,
|
|
@@ -76,7 +74,8 @@ class Term extends Component {
|
|
|
76
74
|
lines: [],
|
|
77
75
|
searchResults: [],
|
|
78
76
|
matchIndex: -1,
|
|
79
|
-
totalLines: 0
|
|
77
|
+
totalLines: 0,
|
|
78
|
+
reconnectCountdown: null
|
|
80
79
|
}
|
|
81
80
|
this.id = `term-${this.props.tab.id}`
|
|
82
81
|
refs.add(this.id, this)
|
|
@@ -1139,10 +1138,13 @@ class Term extends Component {
|
|
|
1139
1138
|
? typeMap.remote
|
|
1140
1139
|
: typeMap.local
|
|
1141
1140
|
})
|
|
1141
|
+
const isAutoReconnect = !!(tab.autoReConnect && this.props.config.autoReconnectTerminal)
|
|
1142
1142
|
const r = await createTerm(opts)
|
|
1143
1143
|
.catch(err => {
|
|
1144
|
-
|
|
1145
|
-
|
|
1144
|
+
if (!isAutoReconnect) {
|
|
1145
|
+
const text = err.message
|
|
1146
|
+
handleErr({ message: text })
|
|
1147
|
+
}
|
|
1146
1148
|
})
|
|
1147
1149
|
if (typeof r === 'string' && r.includes('fail')) {
|
|
1148
1150
|
return this.promote()
|
|
@@ -1154,6 +1156,10 @@ class Term extends Component {
|
|
|
1154
1156
|
loading: false
|
|
1155
1157
|
})
|
|
1156
1158
|
if (!r) {
|
|
1159
|
+
if (isAutoReconnect) {
|
|
1160
|
+
this.scheduleAutoReconnect(3000)
|
|
1161
|
+
return
|
|
1162
|
+
}
|
|
1157
1163
|
this.setStatus(statusMap.error)
|
|
1158
1164
|
return
|
|
1159
1165
|
}
|
|
@@ -1254,46 +1260,70 @@ class Term extends Component {
|
|
|
1254
1260
|
this.setStatus(
|
|
1255
1261
|
statusMap.error
|
|
1256
1262
|
)
|
|
1257
|
-
if (!this.isActiveTerminal() || !window.focused) {
|
|
1258
|
-
return false
|
|
1259
|
-
}
|
|
1260
1263
|
if (this.userTypeExit) {
|
|
1261
1264
|
return this.props.delTab(this.props.tab.id)
|
|
1262
1265
|
}
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
})
|
|
1266
|
+
const { autoReconnectTerminal } = this.props.config
|
|
1267
|
+
const isActive = this.isActiveTerminal()
|
|
1268
|
+
const isFocused = window.focused
|
|
1269
|
+
if (autoReconnectTerminal) {
|
|
1270
|
+
if (isActive && isFocused) {
|
|
1271
|
+
this.socketCloseWarning = showSocketCloseWarning({
|
|
1272
|
+
tabId: this.props.tab.id,
|
|
1273
|
+
tab: this.props.tab,
|
|
1274
|
+
autoReconnect: true,
|
|
1275
|
+
delTab: this.props.delTab,
|
|
1276
|
+
reloadTab: this.props.reloadTab
|
|
1277
|
+
})
|
|
1278
|
+
} else {
|
|
1279
|
+
this.scheduleAutoReconnect(3000)
|
|
1280
|
+
}
|
|
1281
|
+
} else {
|
|
1282
|
+
if (!isActive || !isFocused) {
|
|
1283
|
+
return false
|
|
1284
|
+
}
|
|
1285
|
+
this.socketCloseWarning = showSocketCloseWarning({
|
|
1286
|
+
tabId: this.props.tab.id,
|
|
1287
|
+
tab: this.props.tab,
|
|
1288
|
+
autoReconnect: false,
|
|
1289
|
+
delTab: this.props.delTab,
|
|
1290
|
+
reloadTab: this.props.reloadTab
|
|
1291
|
+
})
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
scheduleAutoReconnect = (delay = 3000) => {
|
|
1296
|
+
clearTimeout(this.timers.reconnectTimer)
|
|
1297
|
+
clearInterval(this.timers.reconnectCountdown)
|
|
1298
|
+
const seconds = Math.round(delay / 1000)
|
|
1299
|
+
this.setState({ reconnectCountdown: seconds })
|
|
1300
|
+
let remaining = seconds
|
|
1301
|
+
this.timers.reconnectCountdown = setInterval(() => {
|
|
1302
|
+
remaining -= 1
|
|
1303
|
+
if (remaining <= 0) {
|
|
1304
|
+
clearInterval(this.timers.reconnectCountdown)
|
|
1305
|
+
this.timers.reconnectCountdown = null
|
|
1306
|
+
}
|
|
1307
|
+
this.setState({ reconnectCountdown: remaining <= 0 ? null : remaining })
|
|
1308
|
+
}, 1000)
|
|
1309
|
+
this.timers.reconnectTimer = setTimeout(() => {
|
|
1310
|
+
clearInterval(this.timers.reconnectCountdown)
|
|
1311
|
+
this.timers.reconnectCountdown = null
|
|
1312
|
+
this.setState({ reconnectCountdown: null })
|
|
1313
|
+
if (this.onClose || !this.props.config.autoReconnectTerminal) {
|
|
1314
|
+
return
|
|
1315
|
+
}
|
|
1316
|
+
const reconnectCount = (this.props.tab.autoReConnect || 0) + 1
|
|
1317
|
+
this.props.reloadTab({ ...this.props.tab, autoReConnect: reconnectCount })
|
|
1318
|
+
}, delay)
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
handleCancelAutoReconnect = () => {
|
|
1322
|
+
clearTimeout(this.timers.reconnectTimer)
|
|
1323
|
+
clearInterval(this.timers.reconnectCountdown)
|
|
1324
|
+
this.timers.reconnectTimer = null
|
|
1325
|
+
this.timers.reconnectCountdown = null
|
|
1326
|
+
this.setState({ reconnectCountdown: null })
|
|
1297
1327
|
}
|
|
1298
1328
|
|
|
1299
1329
|
batchInput = (cmd) => {
|
|
@@ -1395,7 +1425,7 @@ class Term extends Component {
|
|
|
1395
1425
|
totalLines: this.state.totalLines,
|
|
1396
1426
|
height
|
|
1397
1427
|
}
|
|
1398
|
-
const
|
|
1428
|
+
const spin = loading ? <Spin className='loading-wrapper' spinning={loading} /> : null
|
|
1399
1429
|
return (
|
|
1400
1430
|
<Dropdown {...dropdownProps}>
|
|
1401
1431
|
<div
|
|
@@ -1412,7 +1442,11 @@ class Term extends Component {
|
|
|
1412
1442
|
<RemoteFloatControl
|
|
1413
1443
|
isFullScreen={fullscreen}
|
|
1414
1444
|
/>
|
|
1415
|
-
<
|
|
1445
|
+
<ReconnectOverlay
|
|
1446
|
+
countdown={this.state.reconnectCountdown}
|
|
1447
|
+
onCancel={this.handleCancelAutoReconnect}
|
|
1448
|
+
/>
|
|
1449
|
+
{spin}
|
|
1416
1450
|
</div>
|
|
1417
1451
|
</Dropdown>
|
|
1418
1452
|
)
|
|
@@ -141,3 +141,15 @@
|
|
|
141
141
|
.paste-text
|
|
142
142
|
max-height 200px
|
|
143
143
|
overflow-y scroll
|
|
144
|
+
|
|
145
|
+
.terminal-reconnect-overlay
|
|
146
|
+
position absolute
|
|
147
|
+
left 0
|
|
148
|
+
top 0
|
|
149
|
+
width 100%
|
|
150
|
+
height 100%
|
|
151
|
+
display flex
|
|
152
|
+
align-items center
|
|
153
|
+
justify-content center
|
|
154
|
+
z-index 110
|
|
155
|
+
|
package/client/store/tab.js
CHANGED