@electerm/electerm-react 2.17.16 → 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/cache.js +4 -2
- package/client/common/db.js +3 -5
- package/client/common/default-setting.js +1 -1
- package/client/common/pass-enc.js +0 -21
- package/client/common/safe-local-storage.js +106 -1
- package/client/components/ai/ai-chat-history-item.jsx +132 -8
- package/client/components/ai/ai-chat.jsx +10 -159
- package/client/components/ai/ai.styl +6 -1
- package/client/components/bookmark-form/common/render-auth-ssh.jsx +1 -117
- package/client/components/bookmark-form/config/common-fields.js +8 -0
- package/client/components/bookmark-form/config/ftp.js +1 -0
- package/client/components/bookmark-form/config/local.js +10 -51
- package/client/components/session/sessions.jsx +1 -0
- package/client/components/setting-panel/setting-terminal.jsx +2 -2
- 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/sftp/sftp-entry.jsx +35 -12
- package/client/components/sidebar/history.jsx +1 -1
- package/client/components/tabs/app-drag.jsx +13 -12
- 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 +87 -58
- package/client/components/terminal/terminal.styl +12 -0
- package/client/components/terminal/transfer-client-base.js +3 -3
- package/client/components/text-editor/edit-with-custom-editor.jsx +3 -2
- package/client/store/init-state.js +3 -3
- package/client/store/sync.js +0 -1
- package/client/store/tab.js +2 -1
- package/client/store/watch.js +3 -3
- package/package.json +1 -1
|
@@ -1,117 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* bookmark form auth renderer (copied from legacy)
|
|
3
|
-
*/
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
Input,
|
|
7
|
-
AutoComplete,
|
|
8
|
-
Form,
|
|
9
|
-
Select
|
|
10
|
-
} from 'antd'
|
|
11
|
-
import { formItemLayout } from '../../../common/form-layout'
|
|
12
|
-
import { uniqBy } from 'lodash-es'
|
|
13
|
-
import Password from '../../common/password'
|
|
14
|
-
import Upload from '../../common/upload'
|
|
15
|
-
|
|
16
|
-
const { TextArea } = Input
|
|
17
|
-
const FormItem = Form.Item
|
|
18
|
-
const e = window.translate
|
|
19
|
-
|
|
20
|
-
export default function renderAuth (props) {
|
|
21
|
-
const {
|
|
22
|
-
store,
|
|
23
|
-
form,
|
|
24
|
-
authType,
|
|
25
|
-
formItemName = 'password',
|
|
26
|
-
profileFilter = (d) => d
|
|
27
|
-
} = props
|
|
28
|
-
const beforeUpload = async (file) => {
|
|
29
|
-
const filePath = file.filePath
|
|
30
|
-
const privateKey = await window.fs.readFile(filePath)
|
|
31
|
-
form.setFieldsValue({
|
|
32
|
-
privateKey
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
if (authType === 'password') {
|
|
36
|
-
const opts = {
|
|
37
|
-
options: uniqBy(
|
|
38
|
-
store.bookmarks
|
|
39
|
-
.filter(d => d.password),
|
|
40
|
-
(d) => d.password
|
|
41
|
-
)
|
|
42
|
-
.map(d => ({
|
|
43
|
-
label: `${d.title ? `(${d.title})` : ''}${d.username || ''}:${d.host}-******`,
|
|
44
|
-
value: d.password
|
|
45
|
-
})),
|
|
46
|
-
placeholder: e('password'),
|
|
47
|
-
allowClear: false
|
|
48
|
-
}
|
|
49
|
-
return (
|
|
50
|
-
<FormItem
|
|
51
|
-
{...formItemLayout}
|
|
52
|
-
label={e('password')}
|
|
53
|
-
name={formItemName}
|
|
54
|
-
hasFeedback
|
|
55
|
-
rules={[{
|
|
56
|
-
max: 1024, message: '1024 chars max'
|
|
57
|
-
}]}
|
|
58
|
-
>
|
|
59
|
-
<AutoComplete {...opts}>
|
|
60
|
-
<Password />
|
|
61
|
-
</AutoComplete>
|
|
62
|
-
</FormItem>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
if (authType === 'profiles') {
|
|
66
|
-
const opts = {
|
|
67
|
-
options: store.profiles
|
|
68
|
-
.filter(profileFilter)
|
|
69
|
-
.map(d => ({ label: d.name, value: d.id })),
|
|
70
|
-
placeholder: e('profiles'),
|
|
71
|
-
allowClear: true
|
|
72
|
-
}
|
|
73
|
-
return (
|
|
74
|
-
<FormItem
|
|
75
|
-
{...formItemLayout}
|
|
76
|
-
label={e('profiles')}
|
|
77
|
-
name='profile'
|
|
78
|
-
hasFeedback
|
|
79
|
-
>
|
|
80
|
-
<Select {...opts} />
|
|
81
|
-
</FormItem>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
return [
|
|
85
|
-
<FormItem
|
|
86
|
-
{...formItemLayout}
|
|
87
|
-
label={e('privateKey')}
|
|
88
|
-
hasFeedback
|
|
89
|
-
key='privateKey'
|
|
90
|
-
className='mg1b'
|
|
91
|
-
rules={[{
|
|
92
|
-
max: 13000, message: '13000 chars max'
|
|
93
|
-
}]}
|
|
94
|
-
>
|
|
95
|
-
<FormItem noStyle name='privateKey'>
|
|
96
|
-
<TextArea placeholder={e('privateKeyDesc')} autoSize={{ minRows: 1 }} />
|
|
97
|
-
</FormItem>
|
|
98
|
-
<Upload beforeUpload={beforeUpload} fileList={[]}>
|
|
99
|
-
<Button type='dashed' className='mg2b mg1t'>
|
|
100
|
-
{e('importFromFile')}
|
|
101
|
-
</Button>
|
|
102
|
-
</Upload>
|
|
103
|
-
</FormItem>,
|
|
104
|
-
<FormItem
|
|
105
|
-
key='passphrase'
|
|
106
|
-
{...formItemLayout}
|
|
107
|
-
label={e('passphrase')}
|
|
108
|
-
name='passphrase'
|
|
109
|
-
hasFeedback
|
|
110
|
-
rules={[{
|
|
111
|
-
max: 1024, message: '1024 chars max'
|
|
112
|
-
}]}
|
|
113
|
-
>
|
|
114
|
-
<Password placeholder={e('passphraseDesc')} />
|
|
115
|
-
</FormItem>
|
|
116
|
-
]
|
|
117
|
-
}
|
|
1
|
+
export { default } from './ssh-auth-selector'
|
|
@@ -239,6 +239,13 @@ export const commonFields = {
|
|
|
239
239
|
type: 'runScripts',
|
|
240
240
|
name: 'runScripts',
|
|
241
241
|
label: ''
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
enableTerminalImage: {
|
|
245
|
+
type: 'switch',
|
|
246
|
+
name: 'enableTerminalImage',
|
|
247
|
+
label: () => e('enableTerminalImage'),
|
|
248
|
+
valuePropName: 'checked'
|
|
242
249
|
}
|
|
243
250
|
}
|
|
244
251
|
|
|
@@ -272,6 +279,7 @@ export const sshSettings = [
|
|
|
272
279
|
label: () => e('ignoreKeyboardInteractive'),
|
|
273
280
|
valuePropName: 'checked'
|
|
274
281
|
},
|
|
282
|
+
commonFields.enableTerminalImage,
|
|
275
283
|
...terminalSettings.slice(0, -1), // All except terminalBackground
|
|
276
284
|
commonFields.x11,
|
|
277
285
|
commonFields.terminalBackground
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formItemLayout } from '../../../common/form-layout.js'
|
|
2
|
-
import { terminalLocalType
|
|
2
|
+
import { terminalLocalType } from '../../../common/constants.js'
|
|
3
3
|
import {
|
|
4
4
|
createBaseInitValues,
|
|
5
5
|
getTerminalDefaults,
|
|
@@ -31,7 +31,8 @@ const localConfig = {
|
|
|
31
31
|
commonFields.category,
|
|
32
32
|
commonFields.colorTitle,
|
|
33
33
|
commonFields.description,
|
|
34
|
-
|
|
34
|
+
commonFields.enableTerminalImage,
|
|
35
|
+
commonFields.runScripts,
|
|
35
36
|
{ type: 'input', name: 'type', label: 'type', hidden: true }
|
|
36
37
|
]
|
|
37
38
|
},
|
|
@@ -39,54 +40,12 @@ const localConfig = {
|
|
|
39
40
|
key: 'settings',
|
|
40
41
|
label: e('settings'),
|
|
41
42
|
fields: [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{
|
|
49
|
-
type: 'autocomplete',
|
|
50
|
-
name: 'term',
|
|
51
|
-
label: () => e('terminalType'),
|
|
52
|
-
rules: [{ required: true, message: 'terminal type required' }],
|
|
53
|
-
options: terminalTypes.map(t => ({ label: t, value: t }))
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
type: 'switch',
|
|
57
|
-
name: 'displayRaw',
|
|
58
|
-
label: () => e('displayRaw'),
|
|
59
|
-
valuePropName: 'checked'
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
type: 'input',
|
|
63
|
-
name: 'fontFamily',
|
|
64
|
-
label: () => e('fontFamily'),
|
|
65
|
-
rules: [{ max: 130, message: '130 chars max' }],
|
|
66
|
-
props: { placeholder: defaultSettings.fontFamily }
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
type: 'number',
|
|
70
|
-
name: 'fontSize',
|
|
71
|
-
label: () => e('fontSize'),
|
|
72
|
-
props: {
|
|
73
|
-
min: 9,
|
|
74
|
-
max: 65535,
|
|
75
|
-
step: 1,
|
|
76
|
-
placeholder: defaultSettings.fontSize
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
type: 'number',
|
|
81
|
-
name: 'keepaliveInterval',
|
|
82
|
-
label: () => e('keepaliveIntervalDesc'),
|
|
83
|
-
props: {
|
|
84
|
-
min: 0,
|
|
85
|
-
max: 20000000,
|
|
86
|
-
step: 1000
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
{ type: 'terminalBackground', name: 'terminalBackground', label: () => e('terminalBackgroundImage') },
|
|
43
|
+
commonFields.terminalType,
|
|
44
|
+
commonFields.displayRaw,
|
|
45
|
+
commonFields.fontFamily,
|
|
46
|
+
commonFields.fontSize,
|
|
47
|
+
commonFields.keepaliveInterval,
|
|
48
|
+
commonFields.terminalBackground,
|
|
90
49
|
// Exec settings - stored as flat properties on bookmark
|
|
91
50
|
{ type: 'execSettings' }
|
|
92
51
|
]
|
|
@@ -95,7 +54,7 @@ const localConfig = {
|
|
|
95
54
|
key: 'quickCommands',
|
|
96
55
|
label: e('quickCommands'),
|
|
97
56
|
fields: [
|
|
98
|
-
|
|
57
|
+
commonFields.quickCommands
|
|
99
58
|
]
|
|
100
59
|
}
|
|
101
60
|
]
|
|
@@ -580,7 +580,6 @@ export default class SettingTerminal extends Component {
|
|
|
580
580
|
this.renderToggle('saveTerminalLogToFile')
|
|
581
581
|
}
|
|
582
582
|
{this.renderToggle('addTimeStampToTermLog')}
|
|
583
|
-
{this.renderToggle('enableSixel', 'pd2b', 'SIXEL')}
|
|
584
583
|
{
|
|
585
584
|
[
|
|
586
585
|
'cursorBlink',
|
|
@@ -590,7 +589,8 @@ export default class SettingTerminal extends Component {
|
|
|
590
589
|
'ctrlOrMetaOpenTerminalLink',
|
|
591
590
|
'sftpPathFollowSsh',
|
|
592
591
|
'sshSftpSplitView',
|
|
593
|
-
'showCmdSuggestions'
|
|
592
|
+
'showCmdSuggestions',
|
|
593
|
+
'autoReconnectTerminal'
|
|
594
594
|
].map(d => this.renderToggle(d))
|
|
595
595
|
}
|
|
596
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
|
|
@@ -29,7 +29,7 @@ import fs from '../../common/fs'
|
|
|
29
29
|
import ListTable from './list-table-ui'
|
|
30
30
|
import deepCopy from 'json-deep-copy'
|
|
31
31
|
import isValidPath from '../../common/is-valid-path'
|
|
32
|
-
import { LoadingOutlined } from '@ant-design/icons'
|
|
32
|
+
import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons'
|
|
33
33
|
import * as owner from './owner-list'
|
|
34
34
|
import AddressBar from './address-bar'
|
|
35
35
|
import getProxy from '../../common/get-proxy'
|
|
@@ -956,6 +956,20 @@ export default class Sftp extends Component {
|
|
|
956
956
|
}, () => this[`${type}List`](undefined, undefined, oldPath))
|
|
957
957
|
}
|
|
958
958
|
|
|
959
|
+
handleReloadRemoteSftp = async () => {
|
|
960
|
+
if (this.sftp) {
|
|
961
|
+
this.sftp.destroy()
|
|
962
|
+
this.sftp = null
|
|
963
|
+
}
|
|
964
|
+
this.setState({
|
|
965
|
+
remoteLoading: true,
|
|
966
|
+
remote: [],
|
|
967
|
+
remoteFileTree: new Map()
|
|
968
|
+
}, () => {
|
|
969
|
+
this.initRemoteAll()
|
|
970
|
+
})
|
|
971
|
+
}
|
|
972
|
+
|
|
959
973
|
parsePath = (type, pth) => {
|
|
960
974
|
const reg = /^%([^%]+)%/
|
|
961
975
|
if (!reg.test(pth)) {
|
|
@@ -1152,6 +1166,25 @@ export default class Sftp extends Component {
|
|
|
1152
1166
|
)
|
|
1153
1167
|
}
|
|
1154
1168
|
|
|
1169
|
+
renderSftpPanelTitle (type, username, host) {
|
|
1170
|
+
if (type === typeMap.remote) {
|
|
1171
|
+
return (
|
|
1172
|
+
<div className='sftp-panel-title pd1t pd1b pd1x alignright'>
|
|
1173
|
+
<ReloadOutlined
|
|
1174
|
+
className='mg1r pointer'
|
|
1175
|
+
onClick={this.handleReloadRemoteSftp}
|
|
1176
|
+
/>
|
|
1177
|
+
{e('remote')}: {username}@{host}
|
|
1178
|
+
</div>
|
|
1179
|
+
)
|
|
1180
|
+
}
|
|
1181
|
+
return (
|
|
1182
|
+
<div className='sftp-panel-title pd1t pd1b pd1x'>
|
|
1183
|
+
{e('local')}
|
|
1184
|
+
</div>
|
|
1185
|
+
)
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1155
1188
|
renderSection (type, style, width) {
|
|
1156
1189
|
const {
|
|
1157
1190
|
id
|
|
@@ -1222,17 +1255,7 @@ export default class Sftp extends Component {
|
|
|
1222
1255
|
<Spin spinning={loading}>
|
|
1223
1256
|
<div className='pd1 sftp-panel'>
|
|
1224
1257
|
{
|
|
1225
|
-
type
|
|
1226
|
-
? (
|
|
1227
|
-
<div className='sftp-panel-title pd1t pd1b pd1x alignright'>
|
|
1228
|
-
{e('remote')}: {username}@{host}
|
|
1229
|
-
</div>
|
|
1230
|
-
)
|
|
1231
|
-
: (
|
|
1232
|
-
<div className='sftp-panel-title pd1t pd1b pd1x'>
|
|
1233
|
-
{e('local')}
|
|
1234
|
-
</div>
|
|
1235
|
-
)
|
|
1258
|
+
this.renderSftpPanelTitle(type, username, host)
|
|
1236
1259
|
}
|
|
1237
1260
|
<AddressBar
|
|
1238
1261
|
{...addrProps}
|
|
@@ -26,7 +26,7 @@ export default auto(function HistoryPanel (props) {
|
|
|
26
26
|
} = store
|
|
27
27
|
let arr = store.config.disableConnectionHistory ? [] : history
|
|
28
28
|
if (sortByFrequency) {
|
|
29
|
-
arr = arr.sort((a, b) => { return b.count - a.count })
|
|
29
|
+
arr = [...arr].sort((a, b) => { return b.count - a.count })
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const handleSortByFrequencyChange = (checked) => {
|
|
@@ -19,6 +19,19 @@ export default function AppDrag (props) {
|
|
|
19
19
|
return true
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (window.store.shouldSendWindowMove) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
document.addEventListener('mouseup', onMouseUp)
|
|
27
|
+
window.addEventListener('contextmenu', onMouseUp)
|
|
28
|
+
|
|
29
|
+
return () => {
|
|
30
|
+
document.removeEventListener('mouseup', onMouseUp)
|
|
31
|
+
window.removeEventListener('contextmenu', onMouseUp)
|
|
32
|
+
}
|
|
33
|
+
}, [])
|
|
34
|
+
|
|
22
35
|
function onMouseDown (e) {
|
|
23
36
|
// e.stopPropagation()
|
|
24
37
|
if (canOperate(e)) {
|
|
@@ -48,18 +61,6 @@ export default function AppDrag (props) {
|
|
|
48
61
|
window.pre.runGlobalAsync('maximize')
|
|
49
62
|
}
|
|
50
63
|
}
|
|
51
|
-
if (!window.store.shouldSendWindowMove) {
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
// Listen for mouseup at document level to catch mouseup outside window
|
|
54
|
-
document.addEventListener('mouseup', onMouseUp)
|
|
55
|
-
window.addEventListener('contextmenu', onMouseUp)
|
|
56
|
-
|
|
57
|
-
return () => {
|
|
58
|
-
document.removeEventListener('mouseup', onMouseUp)
|
|
59
|
-
window.removeEventListener('contextmenu', onMouseUp)
|
|
60
|
-
}
|
|
61
|
-
}, [])
|
|
62
|
-
}
|
|
63
64
|
const props0 = {
|
|
64
65
|
className: 'app-drag',
|
|
65
66
|
onDoubleClick
|
|
@@ -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
|
+
}
|