@electerm/electerm-react 1.38.60 → 1.38.70
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/constants.js +3 -2
- package/client/common/create-title.jsx +9 -1
- package/client/common/sftp.js +3 -0
- package/client/components/batch-op/batch-op.jsx +1 -6
- package/client/components/bookmark-form/bookmark-form.styl +3 -1
- package/client/components/bookmark-form/render-ssh-tunnel.jsx +210 -88
- package/client/components/bookmark-form/ssh-form-ui.jsx +1 -1
- package/client/components/main/main.jsx +14 -0
- package/client/components/quick-commands/quick-commands-box.jsx +5 -4
- package/client/components/sftp/{confirm-modal.jsx → confirm-modal-store.jsx} +81 -50
- package/client/components/sftp/file-item.jsx +2 -0
- package/client/components/sftp/sftp-entry.jsx +34 -40
- package/client/components/sftp/transfer-conflict-store.jsx +291 -0
- package/client/components/sftp/transport-action-store.jsx +430 -0
- package/client/components/sftp/transports-action-store.jsx +102 -0
- package/client/components/sftp/transports-ui-store.jsx +30 -0
- package/client/components/sidebar/transfer-list-control.jsx +5 -14
- package/client/components/sidebar/transport-ui.jsx +2 -12
- package/client/components/tabs/tab.jsx +43 -2
- package/client/components/tabs/tabs.styl +1 -1
- package/client/components/terminal/index.jsx +1 -0
- package/client/components/terminal/terminal-interactive.jsx +15 -0
- package/client/components/terminal-info/disk.jsx +9 -0
- package/client/store/index.js +4 -0
- package/client/store/init-state.js +2 -3
- package/client/store/sync.js +5 -2
- package/client/store/tab.js +1 -1
- package/client/store/transfer-list.js +55 -2
- package/client/store/watch.js +0 -8
- package/package.json +1 -1
- package/client/components/sftp/transfer-conflict.jsx +0 -323
- package/client/components/sftp/transport-action.jsx +0 -412
- package/client/components/sftp/transport-entry.jsx +0 -108
- package/client/components/sftp/transport-types.js +0 -8
- package/client/components/sftp/transports-action.jsx +0 -111
- package/client/components/sftp/transports-ui.jsx +0 -93
|
@@ -38,7 +38,7 @@ export const contextMenuPaddingTop = 10
|
|
|
38
38
|
export const sftpControlHeight = 28 + 42 + 33 + 36
|
|
39
39
|
export const sidebarWidth = 43
|
|
40
40
|
export const maxHistory = 50
|
|
41
|
-
export const maxTransport =
|
|
41
|
+
export const maxTransport = 1
|
|
42
42
|
export const maxSftpHistory = 20
|
|
43
43
|
export const maxZoom = 8
|
|
44
44
|
export const minZoom = 0.5
|
|
@@ -278,7 +278,8 @@ export const commonActions = {
|
|
|
278
278
|
closeContextMenu: 'close-context-menu',
|
|
279
279
|
clickContextMenu: 'click-context-menu',
|
|
280
280
|
openContextMenu: 'open-context-menu',
|
|
281
|
-
addTransfer: 'add-transfer'
|
|
281
|
+
addTransfer: 'add-transfer',
|
|
282
|
+
sftpList: 'sftp-list'
|
|
282
283
|
}
|
|
283
284
|
|
|
284
285
|
export const srcsSkipUpgradeCheck = [
|
|
@@ -15,7 +15,7 @@ export default function createTitle (res) {
|
|
|
15
15
|
}
|
|
16
16
|
const {
|
|
17
17
|
host, port, username, title, type,
|
|
18
|
-
path, connectionHoppings
|
|
18
|
+
path, connectionHoppings, sshTunnels
|
|
19
19
|
} = res
|
|
20
20
|
const fixTitle = `${username || ''}@${host}:${port}`
|
|
21
21
|
const extra = host || path ? (path || fixTitle) : ''
|
|
@@ -25,6 +25,14 @@ export default function createTitle (res) {
|
|
|
25
25
|
if (connectionHoppings && connectionHoppings.length) {
|
|
26
26
|
f = `[⋙]${f}`
|
|
27
27
|
}
|
|
28
|
+
if (
|
|
29
|
+
sshTunnels &&
|
|
30
|
+
sshTunnels.length &&
|
|
31
|
+
sshTunnels[0].sshTunnel &&
|
|
32
|
+
sshTunnels[0].sshTunnelRemoteHost
|
|
33
|
+
) {
|
|
34
|
+
f = `[T]${f}`
|
|
35
|
+
}
|
|
28
36
|
if (type) {
|
|
29
37
|
f = `[${type}]${f}`
|
|
30
38
|
}
|
package/client/common/sftp.js
CHANGED
|
@@ -7,6 +7,7 @@ import Transfer from './transfer'
|
|
|
7
7
|
import { transferTypeMap, instSftpKeys as keys } from './constants'
|
|
8
8
|
import initWs from './ws'
|
|
9
9
|
|
|
10
|
+
window.sftps = {}
|
|
10
11
|
const transferKeys = Object.keys(transferTypeMap)
|
|
11
12
|
|
|
12
13
|
class Sftp {
|
|
@@ -56,6 +57,7 @@ class Sftp {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
async destroy () {
|
|
60
|
+
delete window.sftps[this.sessionId]
|
|
59
61
|
const { ws } = this
|
|
60
62
|
ws.s({
|
|
61
63
|
action: 'sftp-destroy',
|
|
@@ -69,5 +71,6 @@ class Sftp {
|
|
|
69
71
|
export default async (sessionId) => {
|
|
70
72
|
const sftp = new Sftp()
|
|
71
73
|
await sftp.init(sessionId)
|
|
74
|
+
window.sftps[sessionId] = sftp
|
|
72
75
|
return sftp
|
|
73
76
|
}
|
|
@@ -26,7 +26,6 @@ import { autoRun } from 'manate'
|
|
|
26
26
|
import { pick } from 'lodash-es'
|
|
27
27
|
import { runCmd } from '../terminal/terminal-apis'
|
|
28
28
|
import deepCopy from 'json-deep-copy'
|
|
29
|
-
import postMsg from '../../common/post-msg'
|
|
30
29
|
import uid from '../../common/uid'
|
|
31
30
|
import wait from '../../common/wait'
|
|
32
31
|
import { getFolderFromFilePath } from '../sftp/file-read'
|
|
@@ -186,11 +185,7 @@ export default class BatchOp extends Component {
|
|
|
186
185
|
zip: true,
|
|
187
186
|
skipConfirm: true
|
|
188
187
|
}
|
|
189
|
-
|
|
190
|
-
list: [obj],
|
|
191
|
-
action: commonActions.addTransfer,
|
|
192
|
-
sessionId: tab.sessionId
|
|
193
|
-
})
|
|
188
|
+
window.store.addTransferList([obj])
|
|
194
189
|
const { store } = window
|
|
195
190
|
this.tm = setTimeout(() => {
|
|
196
191
|
reject(new Error('timeout'))
|
|
@@ -5,30 +5,154 @@ import {
|
|
|
5
5
|
Radio,
|
|
6
6
|
Space,
|
|
7
7
|
Button,
|
|
8
|
-
Tooltip
|
|
8
|
+
Tooltip,
|
|
9
|
+
Table
|
|
9
10
|
} from 'antd'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { useState } from 'react'
|
|
12
|
+
import { PlusOutlined, QuestionCircleOutlined, MinusCircleFilled } from '@ant-design/icons'
|
|
13
|
+
import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
|
|
14
|
+
import uid from '../../common/uid'
|
|
12
15
|
|
|
13
16
|
const FormItem = Form.Item
|
|
14
|
-
const FormList = Form.List
|
|
15
17
|
const {
|
|
16
18
|
Button: RadioButton,
|
|
17
19
|
Group: RadioGroup
|
|
18
20
|
} = Radio
|
|
19
21
|
const { prefix } = window
|
|
20
22
|
const e = prefix('ssh')
|
|
23
|
+
const s = prefix('sftp')
|
|
24
|
+
const m = prefix('menu')
|
|
21
25
|
|
|
22
|
-
export default function
|
|
23
|
-
|
|
26
|
+
export default function renderSshTunnels (props) {
|
|
27
|
+
const {
|
|
28
|
+
form,
|
|
29
|
+
formData
|
|
30
|
+
} = props
|
|
31
|
+
const [formChild] = Form.useForm()
|
|
32
|
+
const [initialValues] = useState({
|
|
33
|
+
sshTunnelLocalPort: 12200,
|
|
34
|
+
sshTunnelLocalHost: '127.0.0.1',
|
|
35
|
+
sshTunnelRemotePort: 12300,
|
|
36
|
+
sshTunnelRemoteHost: '127.0.0.1'
|
|
37
|
+
})
|
|
38
|
+
const [list, setList] = useState(formData.sshTunnels || [])
|
|
39
|
+
function onSubmit () {
|
|
40
|
+
formChild.submit()
|
|
41
|
+
}
|
|
42
|
+
function handleFinish (data) {
|
|
43
|
+
const nd = {
|
|
44
|
+
...data,
|
|
45
|
+
id: uid()
|
|
46
|
+
}
|
|
47
|
+
const v = [
|
|
48
|
+
...form.getFieldValue('sshTunnels'),
|
|
49
|
+
nd
|
|
50
|
+
]
|
|
51
|
+
form.setFieldsValue({
|
|
52
|
+
sshTunnels: v
|
|
53
|
+
})
|
|
54
|
+
setList(old => {
|
|
55
|
+
return [
|
|
56
|
+
...old,
|
|
57
|
+
data
|
|
58
|
+
]
|
|
59
|
+
})
|
|
60
|
+
formChild.resetFields()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function remove (id) {
|
|
64
|
+
setList(old => {
|
|
65
|
+
return old.filter(i => i.id !== id)
|
|
66
|
+
})
|
|
67
|
+
const v = form.getFieldValue('sshTunnels').filter(i => i.id !== id)
|
|
68
|
+
form.setFieldsValue({
|
|
69
|
+
sshTunnels: v
|
|
70
|
+
})
|
|
71
|
+
formChild.resetFields()
|
|
72
|
+
}
|
|
73
|
+
const cols = [
|
|
74
|
+
{
|
|
75
|
+
title: 'NO.',
|
|
76
|
+
dataIndex: 'index',
|
|
77
|
+
key: 'index',
|
|
78
|
+
render: (k) => k
|
|
79
|
+
}, {
|
|
80
|
+
title: e('sshTunnel'),
|
|
81
|
+
key: 'sshTunnel',
|
|
82
|
+
render: (k, item) => {
|
|
83
|
+
// sshTunnel is forwardRemoteToLocal or forwardLocalToRemote
|
|
84
|
+
const {
|
|
85
|
+
sshTunnel,
|
|
86
|
+
sshTunnelRemoteHost = '127.0.0.1',
|
|
87
|
+
sshTunnelRemotePort,
|
|
88
|
+
sshTunnelLocalHost = '127.0.0.1',
|
|
89
|
+
sshTunnelLocalPort,
|
|
90
|
+
name
|
|
91
|
+
} = item
|
|
92
|
+
const to = sshTunnel === 'forwardRemoteToLocal'
|
|
93
|
+
? `${s('local')}:${sshTunnelLocalHost}:${sshTunnelLocalPort}`
|
|
94
|
+
: `${s('remote')}:${sshTunnelRemoteHost}:${sshTunnelRemotePort}`
|
|
95
|
+
const from = sshTunnel === 'forwardRemoteToLocal'
|
|
96
|
+
? `${s('remote')}:${sshTunnelRemoteHost}:${sshTunnelRemotePort}`
|
|
97
|
+
: `${s('local')}:${sshTunnelLocalHost}:${sshTunnelLocalPort}`
|
|
98
|
+
return (
|
|
99
|
+
<span>
|
|
100
|
+
{name ? `[${name}] ` : ''}→ {from} → {to}
|
|
101
|
+
</span>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}, {
|
|
105
|
+
title: m('del'),
|
|
106
|
+
key: 'op',
|
|
107
|
+
dataIndex: 'id',
|
|
108
|
+
render: (id) => {
|
|
109
|
+
return (
|
|
110
|
+
<MinusCircleFilled
|
|
111
|
+
className='pointer'
|
|
112
|
+
onClick={() => remove(id)}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
function renderList () {
|
|
24
120
|
return (
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
121
|
+
<FormItem {...tailFormItemLayout}>
|
|
122
|
+
<Table
|
|
123
|
+
columns={cols}
|
|
124
|
+
className='mg3b'
|
|
125
|
+
pagination={false}
|
|
126
|
+
size='small'
|
|
127
|
+
dataSource={list.map((d, i) => {
|
|
128
|
+
return {
|
|
129
|
+
...d,
|
|
130
|
+
index: i + 1
|
|
131
|
+
}
|
|
132
|
+
})}
|
|
133
|
+
/>
|
|
134
|
+
</FormItem>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div>
|
|
140
|
+
<FormItem
|
|
141
|
+
name='sshTunnels'
|
|
142
|
+
className='hide'
|
|
143
|
+
>
|
|
144
|
+
<Input />
|
|
145
|
+
</FormItem>
|
|
146
|
+
<Form
|
|
147
|
+
form={formChild}
|
|
148
|
+
onFinish={handleFinish}
|
|
149
|
+
initialValues={initialValues}
|
|
28
150
|
>
|
|
151
|
+
{renderList()}
|
|
29
152
|
<FormItem
|
|
30
153
|
label={e('sshTunnel')}
|
|
31
|
-
name=
|
|
154
|
+
name='sshTunnel'
|
|
155
|
+
{...formItemLayout}
|
|
32
156
|
required
|
|
33
157
|
>
|
|
34
158
|
<RadioGroup>
|
|
@@ -48,87 +172,85 @@ export default function renderSshTunnel () {
|
|
|
48
172
|
</RadioButton>
|
|
49
173
|
</RadioGroup>
|
|
50
174
|
</FormItem>
|
|
51
|
-
<Space.Compact className='mg2x'>
|
|
52
|
-
<FormItem
|
|
53
|
-
label={e('destination')}
|
|
54
|
-
name={[field.name, 'sshTunnelRemoteHost']}
|
|
55
|
-
initialValue='127.0.0.1'
|
|
56
|
-
required
|
|
57
|
-
>
|
|
58
|
-
<Input
|
|
59
|
-
className='compact-input'
|
|
60
|
-
placeholder={e('host')}
|
|
61
|
-
/>
|
|
62
|
-
</FormItem>
|
|
63
|
-
<FormItem
|
|
64
|
-
label=''
|
|
65
|
-
name={[field.name, 'sshTunnelRemotePort']}
|
|
66
|
-
initialValue={22}
|
|
67
|
-
required
|
|
68
|
-
>
|
|
69
|
-
<InputNumber
|
|
70
|
-
min={1}
|
|
71
|
-
max={65535}
|
|
72
|
-
// addonBefore={e('remotePort')}
|
|
73
|
-
className='compact-input'
|
|
74
|
-
placeholder={e('port')}
|
|
75
|
-
/>
|
|
76
|
-
</FormItem>
|
|
77
|
-
</Space.Compact>
|
|
78
175
|
<FormItem
|
|
79
|
-
label={
|
|
80
|
-
|
|
81
|
-
initialValue={22}
|
|
176
|
+
label={s('remote')}
|
|
177
|
+
{...formItemLayout}
|
|
82
178
|
required
|
|
83
|
-
className='
|
|
179
|
+
className='ssh-tunnels-host'
|
|
84
180
|
>
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
181
|
+
<Space.Compact>
|
|
182
|
+
<FormItem
|
|
183
|
+
name='sshTunnelRemoteHost'
|
|
184
|
+
label=''
|
|
185
|
+
required
|
|
186
|
+
>
|
|
187
|
+
<Input
|
|
188
|
+
placeholder={e('host')}
|
|
189
|
+
/>
|
|
190
|
+
</FormItem>
|
|
191
|
+
<FormItem
|
|
192
|
+
label=''
|
|
193
|
+
name='sshTunnelRemotePort'
|
|
194
|
+
required
|
|
195
|
+
>
|
|
196
|
+
<InputNumber
|
|
197
|
+
min={1}
|
|
198
|
+
max={65535}
|
|
199
|
+
placeholder={e('port')}
|
|
200
|
+
/>
|
|
201
|
+
</FormItem>
|
|
202
|
+
</Space.Compact>
|
|
203
|
+
</FormItem>
|
|
204
|
+
<FormItem
|
|
205
|
+
label={s('local')}
|
|
206
|
+
{...formItemLayout}
|
|
207
|
+
required
|
|
208
|
+
className='ssh-tunnels-host'
|
|
209
|
+
>
|
|
210
|
+
<Space.Compact>
|
|
211
|
+
<FormItem
|
|
212
|
+
name='sshTunnelLocalHost'
|
|
213
|
+
label=''
|
|
214
|
+
required
|
|
215
|
+
>
|
|
216
|
+
<Input
|
|
217
|
+
placeholder={e('host')}
|
|
218
|
+
/>
|
|
219
|
+
</FormItem>
|
|
220
|
+
<FormItem
|
|
221
|
+
label=''
|
|
222
|
+
name='sshTunnelLocalPort'
|
|
223
|
+
required
|
|
224
|
+
>
|
|
225
|
+
<InputNumber
|
|
226
|
+
min={1}
|
|
227
|
+
max={65535}
|
|
228
|
+
// addonBefore={e('localPort')}
|
|
229
|
+
placeholder={e('port')}
|
|
230
|
+
/>
|
|
231
|
+
</FormItem>
|
|
232
|
+
</Space.Compact>
|
|
233
|
+
</FormItem>
|
|
234
|
+
<FormItem
|
|
235
|
+
name='name'
|
|
236
|
+
label={s('name')}
|
|
237
|
+
{...formItemLayout}
|
|
238
|
+
>
|
|
239
|
+
<Input
|
|
240
|
+
placeholder={e('name')}
|
|
91
241
|
/>
|
|
92
242
|
</FormItem>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
name='sshTunnels'
|
|
107
|
-
key='sshTunnels'
|
|
108
|
-
>
|
|
109
|
-
{
|
|
110
|
-
(fields, { add, remove }, { errors }) => {
|
|
111
|
-
return (
|
|
112
|
-
<div>
|
|
113
|
-
{
|
|
114
|
-
fields.map((field, i) => {
|
|
115
|
-
return renderItem(field, i, add, remove)
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
<FormItem>
|
|
119
|
-
<Button
|
|
120
|
-
type='dashed'
|
|
121
|
-
onClick={() => add()}
|
|
122
|
-
block
|
|
123
|
-
icon={<PlusOutlined />}
|
|
124
|
-
>
|
|
125
|
-
{e('sshTunnel')}
|
|
126
|
-
</Button>
|
|
127
|
-
</FormItem>
|
|
128
|
-
</div>
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
</FormList>
|
|
133
|
-
]
|
|
243
|
+
<FormItem {...tailFormItemLayout} className='mg60b'>
|
|
244
|
+
<Button
|
|
245
|
+
type='default'
|
|
246
|
+
htmlType='button'
|
|
247
|
+
icon={<PlusOutlined />}
|
|
248
|
+
onClick={onSubmit}
|
|
249
|
+
>
|
|
250
|
+
{e('sshTunnel')}
|
|
251
|
+
</Button>
|
|
252
|
+
</FormItem>
|
|
253
|
+
</Form>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
134
256
|
}
|
|
@@ -12,6 +12,9 @@ import CssOverwrite from './css-overwrite'
|
|
|
12
12
|
import UiTheme from './ui-theme'
|
|
13
13
|
import CustomCss from './custom-css.jsx'
|
|
14
14
|
import TerminalInteractive from '../terminal/terminal-interactive'
|
|
15
|
+
import ConfirmModalStore from '../sftp/confirm-modal-store.jsx'
|
|
16
|
+
import TransferConflictStore from '../sftp/transfer-conflict-store.jsx'
|
|
17
|
+
import TransportsActionStore from '../sftp/transports-action-store.jsx'
|
|
15
18
|
import classnames from 'classnames'
|
|
16
19
|
import ShortcutControl from '../shortcuts/shortcut-control.jsx'
|
|
17
20
|
import { isMac, isWin } from '../../common/constants'
|
|
@@ -149,6 +152,17 @@ export default class Index extends Component {
|
|
|
149
152
|
/>
|
|
150
153
|
</div>
|
|
151
154
|
<ContextMenu store={store} />
|
|
155
|
+
<ConfirmModalStore
|
|
156
|
+
store={store}
|
|
157
|
+
/>
|
|
158
|
+
<TransferConflictStore
|
|
159
|
+
store={store}
|
|
160
|
+
_fileTransfers={store._fileTransfers}
|
|
161
|
+
/>
|
|
162
|
+
<TransportsActionStore
|
|
163
|
+
store={store}
|
|
164
|
+
_fileTransfers={store._fileTransfers}
|
|
165
|
+
/>
|
|
152
166
|
</div>
|
|
153
167
|
</ConfigProvider>
|
|
154
168
|
)
|
|
@@ -182,7 +182,7 @@ export default class QuickCommandsFooterBox extends Component {
|
|
|
182
182
|
sortArray (array, keyword, labels, qmSortByFrequency) {
|
|
183
183
|
const sorters = [
|
|
184
184
|
(obj) => !(keyword && obj.name.toLowerCase().includes(keyword)),
|
|
185
|
-
(obj) => !labels.some((label) => obj.labels.includes(label))
|
|
185
|
+
(obj) => !labels.some((label) => (obj.labels || []).includes(label))
|
|
186
186
|
]
|
|
187
187
|
if (qmSortByFrequency) {
|
|
188
188
|
sorters.push((obj) => -(obj.clickCount || 0))
|
|
@@ -194,9 +194,10 @@ export default class QuickCommandsFooterBox extends Component {
|
|
|
194
194
|
const {
|
|
195
195
|
openQuickCommandBar,
|
|
196
196
|
pinnedQuickCommandBar,
|
|
197
|
-
qmSortByFrequency
|
|
197
|
+
qmSortByFrequency,
|
|
198
|
+
inActiveTerminal
|
|
198
199
|
} = this.props.store
|
|
199
|
-
if (!openQuickCommandBar && !pinnedQuickCommandBar) {
|
|
200
|
+
if ((!openQuickCommandBar && !pinnedQuickCommandBar) || !inActiveTerminal) {
|
|
200
201
|
return null
|
|
201
202
|
}
|
|
202
203
|
const all = this.props.store.currentQuickCommands
|
|
@@ -210,7 +211,7 @@ export default class QuickCommandsFooterBox extends Component {
|
|
|
210
211
|
return {
|
|
211
212
|
...d,
|
|
212
213
|
nameMatch: keyword && d.name.toLowerCase().includes(keyword),
|
|
213
|
-
labelMatch: labels.some((label) => d.labels.includes(label))
|
|
214
|
+
labelMatch: labels.some((label) => (d.labels || []).includes(label))
|
|
214
215
|
}
|
|
215
216
|
})
|
|
216
217
|
const sprops = {
|