@electerm/electerm-react 3.5.6 → 3.6.16
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/ws.js +16 -5
- package/client/components/ai/ai-history.jsx +0 -6
- package/client/components/bookmark-form/common/ssh-tunnel-form.jsx +183 -0
- package/client/components/bookmark-form/common/ssh-tunnels.jsx +81 -173
- package/client/components/main/upgrade.jsx +2 -1
- package/client/components/shortcuts/shortcut-control.jsx +9 -0
- package/client/components/shortcuts/shortcuts-defaults.js +5 -0
- package/client/components/ssh-config/load-ssh-configs.jsx +1 -1
- package/client/components/sys-menu/menu-btn.jsx +2 -1
- package/package.json +1 -1
package/client/common/ws.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import generate from './uid'
|
|
6
6
|
import wait from './wait'
|
|
7
|
-
import copy from 'json-deep-copy'
|
|
8
7
|
import { pick } from 'lodash-es'
|
|
9
8
|
|
|
10
9
|
const onces = {}
|
|
@@ -32,7 +31,13 @@ class Ws {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
async once (func, id) {
|
|
34
|
+
const maxWait = 300
|
|
35
|
+
let waited = 0
|
|
35
36
|
while (this.closed) {
|
|
37
|
+
if (++waited >= maxWait) {
|
|
38
|
+
console.warn('ws once timeout waiting for reconnection', id)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
36
41
|
await wait(100)
|
|
37
42
|
}
|
|
38
43
|
this.onceIds.push(id)
|
|
@@ -83,7 +88,7 @@ class Ws {
|
|
|
83
88
|
if (this.eid) {
|
|
84
89
|
delete persists[this.eid]
|
|
85
90
|
}
|
|
86
|
-
const ids =
|
|
91
|
+
const ids = [...this.onceIds]
|
|
87
92
|
ids.forEach(k => {
|
|
88
93
|
delete onces[k]
|
|
89
94
|
})
|
|
@@ -115,10 +120,16 @@ function onEvent (e) {
|
|
|
115
120
|
action,
|
|
116
121
|
persist
|
|
117
122
|
} = e.data
|
|
118
|
-
if (wss[id]) {
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
if (wss[id] && action === 'close') {
|
|
124
|
+
const ws = wss[id]
|
|
125
|
+
ws.onclose()
|
|
126
|
+
if (ws.persist) {
|
|
127
|
+
ws.closed = true
|
|
128
|
+
} else {
|
|
129
|
+
ws.clearOnces()
|
|
130
|
+
delete wss[id]
|
|
121
131
|
}
|
|
132
|
+
return
|
|
122
133
|
}
|
|
123
134
|
if (persists[id]) {
|
|
124
135
|
persists[id].resolve(data)
|
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { useState, useEffect } from 'react'
|
|
5
5
|
import { Space } from 'antd'
|
|
6
|
-
import { HistoryOutlined } from '@ant-design/icons'
|
|
7
6
|
import { safeGetItemJSON, safeSetItemJSON } from '../../common/safe-local-storage'
|
|
8
7
|
import AiHistoryItem from './ai-history-item'
|
|
9
8
|
|
|
10
9
|
const MAX_HISTORY = 20
|
|
11
|
-
const e = window.translate
|
|
12
10
|
|
|
13
11
|
export function getHistory (storageKey) {
|
|
14
12
|
return safeGetItemJSON(storageKey, [])
|
|
@@ -81,10 +79,6 @@ export default function AiHistory (props) {
|
|
|
81
79
|
|
|
82
80
|
return (
|
|
83
81
|
<div className='ai-bookmark-history pd1b'>
|
|
84
|
-
<div className='pd1b text-muted'>
|
|
85
|
-
<HistoryOutlined className='mg1r' />
|
|
86
|
-
<span className='mg1r'>{e('history') || 'History'}:</span>
|
|
87
|
-
</div>
|
|
88
82
|
<Space size={[8, 8]} wrap>
|
|
89
83
|
{history.map((item, index) => {
|
|
90
84
|
const keyStr = typeof item === 'string' ? item : JSON.stringify(item)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Form,
|
|
3
|
+
Input,
|
|
4
|
+
InputNumber,
|
|
5
|
+
Radio,
|
|
6
|
+
Space,
|
|
7
|
+
Button,
|
|
8
|
+
Tooltip
|
|
9
|
+
} from 'antd'
|
|
10
|
+
import {
|
|
11
|
+
PlusOutlined,
|
|
12
|
+
QuestionCircleOutlined,
|
|
13
|
+
SaveOutlined,
|
|
14
|
+
UserOutlined
|
|
15
|
+
} from '@ant-design/icons'
|
|
16
|
+
import { formItemLayout, tailFormItemLayout } from '../../../common/form-layout'
|
|
17
|
+
import { useState } from 'react'
|
|
18
|
+
|
|
19
|
+
const FormItem = Form.Item
|
|
20
|
+
const {
|
|
21
|
+
Button: RadioButton,
|
|
22
|
+
Group: RadioGroup
|
|
23
|
+
} = Radio
|
|
24
|
+
const e = window.translate
|
|
25
|
+
|
|
26
|
+
export default function SshTunnelForm (props) {
|
|
27
|
+
const {
|
|
28
|
+
formChild,
|
|
29
|
+
initialValues,
|
|
30
|
+
onFinish,
|
|
31
|
+
isEdit
|
|
32
|
+
} = props
|
|
33
|
+
|
|
34
|
+
const [isDynamic, setIsDynamic] = useState(
|
|
35
|
+
(initialValues?.sshTunnel || 'forwardRemoteToLocal') === 'dynamicForward'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
function onChange (ev) {
|
|
39
|
+
setIsDynamic(ev.target.value === 'dynamicForward')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function onSubmit () {
|
|
43
|
+
formChild.submit()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderSshTunnelFlow (direction) {
|
|
47
|
+
const localToRemote = direction === 'localToRemote'
|
|
48
|
+
const middle = localToRemote ? e('local') : e('remote')
|
|
49
|
+
const last = localToRemote ? e('remote') : e('local')
|
|
50
|
+
return (
|
|
51
|
+
<div>
|
|
52
|
+
<p>{e(direction)}</p>
|
|
53
|
+
<p><UserOutlined /> → {middle} → {last}</p>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderDynamicForward () {
|
|
59
|
+
return (
|
|
60
|
+
<p><UserOutlined /> → socks proxy → url</p>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function renderRemote () {
|
|
65
|
+
if (isDynamic) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
return (
|
|
69
|
+
<FormItem
|
|
70
|
+
label={e('remote')}
|
|
71
|
+
{...formItemLayout}
|
|
72
|
+
required
|
|
73
|
+
className='ssh-tunnels-host'
|
|
74
|
+
>
|
|
75
|
+
<Space.Compact>
|
|
76
|
+
<FormItem
|
|
77
|
+
name='sshTunnelRemoteHost'
|
|
78
|
+
label=''
|
|
79
|
+
required
|
|
80
|
+
>
|
|
81
|
+
<Input
|
|
82
|
+
placeholder={e('host')}
|
|
83
|
+
/>
|
|
84
|
+
</FormItem>
|
|
85
|
+
<FormItem
|
|
86
|
+
label=''
|
|
87
|
+
name='sshTunnelRemotePort'
|
|
88
|
+
required
|
|
89
|
+
>
|
|
90
|
+
<InputNumber
|
|
91
|
+
min={1}
|
|
92
|
+
max={65535}
|
|
93
|
+
placeholder={e('port')}
|
|
94
|
+
/>
|
|
95
|
+
</FormItem>
|
|
96
|
+
</Space.Compact>
|
|
97
|
+
</FormItem>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Form
|
|
103
|
+
form={formChild}
|
|
104
|
+
onFinish={onFinish}
|
|
105
|
+
initialValues={initialValues}
|
|
106
|
+
component='div'
|
|
107
|
+
>
|
|
108
|
+
<FormItem
|
|
109
|
+
label={e('sshTunnel')}
|
|
110
|
+
name='sshTunnel'
|
|
111
|
+
{...formItemLayout}
|
|
112
|
+
required
|
|
113
|
+
>
|
|
114
|
+
<RadioGroup onChange={onChange}>
|
|
115
|
+
<RadioButton value='forwardRemoteToLocal'>
|
|
116
|
+
<Tooltip title={renderSshTunnelFlow('remoteToLocal')}>
|
|
117
|
+
<span>R→L <QuestionCircleOutlined /></span>
|
|
118
|
+
</Tooltip>
|
|
119
|
+
</RadioButton>
|
|
120
|
+
<RadioButton value='forwardLocalToRemote'>
|
|
121
|
+
<Tooltip title={renderSshTunnelFlow('localToRemote')}>
|
|
122
|
+
<span>L→R <QuestionCircleOutlined /></span>
|
|
123
|
+
</Tooltip>
|
|
124
|
+
</RadioButton>
|
|
125
|
+
<RadioButton value='dynamicForward'>
|
|
126
|
+
<Tooltip title={renderDynamicForward()}>
|
|
127
|
+
<span>{e('dynamicForward')}(socks proxy) <QuestionCircleOutlined /></span>
|
|
128
|
+
</Tooltip>
|
|
129
|
+
</RadioButton>
|
|
130
|
+
</RadioGroup>
|
|
131
|
+
</FormItem>
|
|
132
|
+
{renderRemote()}
|
|
133
|
+
<FormItem
|
|
134
|
+
label={e('local')}
|
|
135
|
+
{...formItemLayout}
|
|
136
|
+
required
|
|
137
|
+
className='ssh-tunnels-host'
|
|
138
|
+
>
|
|
139
|
+
<Space.Compact>
|
|
140
|
+
<FormItem
|
|
141
|
+
name='sshTunnelLocalHost'
|
|
142
|
+
label=''
|
|
143
|
+
required
|
|
144
|
+
>
|
|
145
|
+
<Input
|
|
146
|
+
placeholder={e('host')}
|
|
147
|
+
/>
|
|
148
|
+
</FormItem>
|
|
149
|
+
<FormItem
|
|
150
|
+
label=''
|
|
151
|
+
name='sshTunnelLocalPort'
|
|
152
|
+
required
|
|
153
|
+
>
|
|
154
|
+
<InputNumber
|
|
155
|
+
min={1}
|
|
156
|
+
max={65535}
|
|
157
|
+
placeholder={e('port')}
|
|
158
|
+
/>
|
|
159
|
+
</FormItem>
|
|
160
|
+
</Space.Compact>
|
|
161
|
+
</FormItem>
|
|
162
|
+
<FormItem
|
|
163
|
+
name='name'
|
|
164
|
+
label={e('name')}
|
|
165
|
+
{...formItemLayout}
|
|
166
|
+
>
|
|
167
|
+
<Input
|
|
168
|
+
placeholder={e('name')}
|
|
169
|
+
/>
|
|
170
|
+
</FormItem>
|
|
171
|
+
<FormItem {...tailFormItemLayout} className='mg60b'>
|
|
172
|
+
<Button
|
|
173
|
+
type='default'
|
|
174
|
+
htmlType='button'
|
|
175
|
+
icon={isEdit ? <SaveOutlined /> : <PlusOutlined />}
|
|
176
|
+
onClick={onSubmit}
|
|
177
|
+
>
|
|
178
|
+
{isEdit ? e('save') : e('sshTunnel')}
|
|
179
|
+
</Button>
|
|
180
|
+
</FormItem>
|
|
181
|
+
</Form>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
@@ -1,46 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Form,
|
|
3
3
|
Input,
|
|
4
|
-
InputNumber,
|
|
5
|
-
Radio,
|
|
6
|
-
Space,
|
|
7
|
-
Button,
|
|
8
|
-
Tooltip,
|
|
9
4
|
Table
|
|
10
5
|
} from 'antd'
|
|
11
6
|
import { useState } from 'react'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
7
|
+
import { MinusCircleFilled, EditOutlined } from '@ant-design/icons'
|
|
8
|
+
import { tailFormItemLayout } from '../../../common/form-layout'
|
|
14
9
|
import uid from '../../../common/uid'
|
|
10
|
+
import Modal from '../../common/modal'
|
|
11
|
+
import SshTunnelForm from './ssh-tunnel-form'
|
|
15
12
|
|
|
16
13
|
const FormItem = Form.Item
|
|
17
|
-
const {
|
|
18
|
-
Button: RadioButton,
|
|
19
|
-
Group: RadioGroup
|
|
20
|
-
} = Radio
|
|
21
14
|
const e = window.translate
|
|
22
15
|
|
|
16
|
+
const defaultInitialValues = {
|
|
17
|
+
sshTunnel: 'forwardRemoteToLocal',
|
|
18
|
+
sshTunnelLocalPort: 12200,
|
|
19
|
+
sshTunnelLocalHost: '127.0.0.1',
|
|
20
|
+
sshTunnelRemotePort: 12300,
|
|
21
|
+
sshTunnelRemoteHost: '127.0.0.1'
|
|
22
|
+
}
|
|
23
|
+
|
|
23
24
|
export default function renderSshTunnels (props) {
|
|
24
25
|
const {
|
|
25
26
|
form,
|
|
26
27
|
formData
|
|
27
28
|
} = props
|
|
28
29
|
const [formChild] = Form.useForm()
|
|
29
|
-
const [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
sshTunnelRemotePort: 12300,
|
|
34
|
-
sshTunnelRemoteHost: '127.0.0.1'
|
|
35
|
-
})
|
|
36
|
-
const [isDynamic, setter] = useState(formData.sshTunnel === 'dynamicForward')
|
|
30
|
+
const [editFormChild] = Form.useForm()
|
|
31
|
+
const [initialValues] = useState(defaultInitialValues)
|
|
32
|
+
const [editModalVisible, setEditModalVisible] = useState(false)
|
|
33
|
+
const [editingItem, setEditingItem] = useState(null)
|
|
37
34
|
const [list, setList] = useState(formData.sshTunnels || [])
|
|
38
|
-
function onSubmit () {
|
|
39
|
-
formChild.submit()
|
|
40
|
-
}
|
|
41
|
-
function onChange (e) {
|
|
42
|
-
setter(e.target.value === 'dynamicForward')
|
|
43
|
-
}
|
|
44
35
|
function handleFinish (data) {
|
|
45
36
|
const nd = {
|
|
46
37
|
...data,
|
|
@@ -56,7 +47,7 @@ export default function renderSshTunnels (props) {
|
|
|
56
47
|
setList(old => {
|
|
57
48
|
return [
|
|
58
49
|
...old,
|
|
59
|
-
|
|
50
|
+
nd
|
|
60
51
|
]
|
|
61
52
|
})
|
|
62
53
|
formChild.resetFields()
|
|
@@ -72,6 +63,38 @@ export default function renderSshTunnels (props) {
|
|
|
72
63
|
})
|
|
73
64
|
formChild.resetFields()
|
|
74
65
|
}
|
|
66
|
+
|
|
67
|
+
function openEdit (record) {
|
|
68
|
+
setEditingItem(record)
|
|
69
|
+
setEditModalVisible(true)
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
editFormChild.setFieldsValue(record)
|
|
72
|
+
}, 100)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handleEditFinish (data) {
|
|
76
|
+
const updatedItem = {
|
|
77
|
+
...data,
|
|
78
|
+
id: editingItem.id
|
|
79
|
+
}
|
|
80
|
+
setList(old => old.map(item => item.id === editingItem.id ? updatedItem : item))
|
|
81
|
+
const v = (form.getFieldValue('sshTunnels') || []).map(
|
|
82
|
+
item => item.id === editingItem.id ? updatedItem : item
|
|
83
|
+
)
|
|
84
|
+
form.setFieldsValue({
|
|
85
|
+
sshTunnels: v
|
|
86
|
+
})
|
|
87
|
+
setEditModalVisible(false)
|
|
88
|
+
setEditingItem(null)
|
|
89
|
+
editFormChild.resetFields()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function closeEditModal () {
|
|
93
|
+
setEditModalVisible(false)
|
|
94
|
+
setEditingItem(null)
|
|
95
|
+
editFormChild.resetFields()
|
|
96
|
+
}
|
|
97
|
+
|
|
75
98
|
const cols = [
|
|
76
99
|
{
|
|
77
100
|
title: 'NO.',
|
|
@@ -108,15 +131,21 @@ export default function renderSshTunnels (props) {
|
|
|
108
131
|
)
|
|
109
132
|
}
|
|
110
133
|
}, {
|
|
111
|
-
title: e('
|
|
134
|
+
title: e('op'),
|
|
112
135
|
key: 'op',
|
|
113
136
|
dataIndex: 'id',
|
|
114
|
-
render: (id) => {
|
|
137
|
+
render: (id, record) => {
|
|
115
138
|
return (
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
139
|
+
<span>
|
|
140
|
+
<EditOutlined
|
|
141
|
+
className='pointer mg1r'
|
|
142
|
+
onClick={() => openEdit(record)}
|
|
143
|
+
/>
|
|
144
|
+
<MinusCircleFilled
|
|
145
|
+
className='pointer'
|
|
146
|
+
onClick={() => remove(id)}
|
|
147
|
+
/>
|
|
148
|
+
</span>
|
|
120
149
|
)
|
|
121
150
|
}
|
|
122
151
|
}
|
|
@@ -141,62 +170,6 @@ export default function renderSshTunnels (props) {
|
|
|
141
170
|
)
|
|
142
171
|
}
|
|
143
172
|
|
|
144
|
-
// direction = localToRemote or remoteToLocal, should render user, remote port, local port visit directions connected with arrows accordingly
|
|
145
|
-
function renderSshTunnelFlow (direction) {
|
|
146
|
-
const localToRemote = direction === 'localToRemote'
|
|
147
|
-
const middle = localToRemote ? e('local') : e('remote')
|
|
148
|
-
const last = localToRemote ? e('remote') : e('local')
|
|
149
|
-
return (
|
|
150
|
-
<div>
|
|
151
|
-
<p>{e(direction)}</p>
|
|
152
|
-
<p><UserOutlined /> → {middle} → {last}</p>
|
|
153
|
-
</div>
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function renderDynamicForward () {
|
|
158
|
-
return (
|
|
159
|
-
<p><UserOutlined /> → socks proxy → url</p>
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function renderRemote () {
|
|
164
|
-
if (isDynamic) {
|
|
165
|
-
return null
|
|
166
|
-
}
|
|
167
|
-
return (
|
|
168
|
-
<FormItem
|
|
169
|
-
label={e('remote')}
|
|
170
|
-
{...formItemLayout}
|
|
171
|
-
required
|
|
172
|
-
className='ssh-tunnels-host'
|
|
173
|
-
>
|
|
174
|
-
<Space.Compact>
|
|
175
|
-
<FormItem
|
|
176
|
-
name='sshTunnelRemoteHost'
|
|
177
|
-
label=''
|
|
178
|
-
required
|
|
179
|
-
>
|
|
180
|
-
<Input
|
|
181
|
-
placeholder={e('host')}
|
|
182
|
-
/>
|
|
183
|
-
</FormItem>
|
|
184
|
-
<FormItem
|
|
185
|
-
label=''
|
|
186
|
-
name='sshTunnelRemotePort'
|
|
187
|
-
required
|
|
188
|
-
>
|
|
189
|
-
<InputNumber
|
|
190
|
-
min={1}
|
|
191
|
-
max={65535}
|
|
192
|
-
placeholder={e('port')}
|
|
193
|
-
/>
|
|
194
|
-
</FormItem>
|
|
195
|
-
</Space.Compact>
|
|
196
|
-
</FormItem>
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
173
|
return (
|
|
201
174
|
<>
|
|
202
175
|
<FormItem
|
|
@@ -205,94 +178,29 @@ export default function renderSshTunnels (props) {
|
|
|
205
178
|
>
|
|
206
179
|
<Input />
|
|
207
180
|
</FormItem>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
181
|
+
{renderList()}
|
|
182
|
+
<SshTunnelForm
|
|
183
|
+
formChild={formChild}
|
|
211
184
|
initialValues={initialValues}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
>
|
|
222
|
-
<RadioGroup onChange={onChange}>
|
|
223
|
-
<RadioButton
|
|
224
|
-
value='forwardRemoteToLocal'
|
|
225
|
-
>
|
|
226
|
-
<Tooltip title={renderSshTunnelFlow('remoteToLocal')}>
|
|
227
|
-
<span>R→L <QuestionCircleOutlined /></span>
|
|
228
|
-
</Tooltip>
|
|
229
|
-
</RadioButton>
|
|
230
|
-
<RadioButton
|
|
231
|
-
value='forwardLocalToRemote'
|
|
232
|
-
>
|
|
233
|
-
<Tooltip title={renderSshTunnelFlow('localToRemote')}>
|
|
234
|
-
<span>L→R <QuestionCircleOutlined /></span>
|
|
235
|
-
</Tooltip>
|
|
236
|
-
</RadioButton>
|
|
237
|
-
<RadioButton
|
|
238
|
-
value='dynamicForward'
|
|
239
|
-
>
|
|
240
|
-
<Tooltip title={renderDynamicForward()}>
|
|
241
|
-
<span>{e('dynamicForward')}(socks proxy) <QuestionCircleOutlined /></span>
|
|
242
|
-
</Tooltip>
|
|
243
|
-
</RadioButton>
|
|
244
|
-
</RadioGroup>
|
|
245
|
-
</FormItem>
|
|
246
|
-
{renderRemote()}
|
|
247
|
-
<FormItem
|
|
248
|
-
label={e('local')}
|
|
249
|
-
{...formItemLayout}
|
|
250
|
-
required
|
|
251
|
-
className='ssh-tunnels-host'
|
|
252
|
-
>
|
|
253
|
-
<Space.Compact>
|
|
254
|
-
<FormItem
|
|
255
|
-
name='sshTunnelLocalHost'
|
|
256
|
-
label=''
|
|
257
|
-
required
|
|
258
|
-
>
|
|
259
|
-
<Input
|
|
260
|
-
placeholder={e('host')}
|
|
261
|
-
/>
|
|
262
|
-
</FormItem>
|
|
263
|
-
<FormItem
|
|
264
|
-
label=''
|
|
265
|
-
name='sshTunnelLocalPort'
|
|
266
|
-
required
|
|
267
|
-
>
|
|
268
|
-
<InputNumber
|
|
269
|
-
min={1}
|
|
270
|
-
max={65535}
|
|
271
|
-
placeholder={e('port')}
|
|
272
|
-
/>
|
|
273
|
-
</FormItem>
|
|
274
|
-
</Space.Compact>
|
|
275
|
-
</FormItem>
|
|
276
|
-
<FormItem
|
|
277
|
-
name='name'
|
|
278
|
-
label={e('name')}
|
|
279
|
-
{...formItemLayout}
|
|
185
|
+
onFinish={handleFinish}
|
|
186
|
+
/>
|
|
187
|
+
{editModalVisible && (
|
|
188
|
+
<Modal
|
|
189
|
+
open={editModalVisible}
|
|
190
|
+
onCancel={closeEditModal}
|
|
191
|
+
footer={null}
|
|
192
|
+
title={e('edit') + ' ' + e('sshTunnel')}
|
|
193
|
+
width={600}
|
|
280
194
|
>
|
|
281
|
-
<
|
|
282
|
-
|
|
195
|
+
<SshTunnelForm
|
|
196
|
+
key={editingItem?.id}
|
|
197
|
+
formChild={editFormChild}
|
|
198
|
+
initialValues={editingItem}
|
|
199
|
+
onFinish={handleEditFinish}
|
|
200
|
+
isEdit
|
|
283
201
|
/>
|
|
284
|
-
</
|
|
285
|
-
|
|
286
|
-
<Button
|
|
287
|
-
type='default'
|
|
288
|
-
htmlType='button'
|
|
289
|
-
icon={<PlusOutlined />}
|
|
290
|
-
onClick={onSubmit}
|
|
291
|
-
>
|
|
292
|
-
{e('sshTunnel')}
|
|
293
|
-
</Button>
|
|
294
|
-
</FormItem>
|
|
295
|
-
</Form>
|
|
202
|
+
</Modal>
|
|
203
|
+
)}
|
|
296
204
|
</>
|
|
297
205
|
)
|
|
298
206
|
}
|
|
@@ -172,6 +172,15 @@ class ShortcutControl extends React.PureComponent {
|
|
|
172
172
|
window.store.onNewSsh()
|
|
173
173
|
}, 500)
|
|
174
174
|
|
|
175
|
+
newTabShortcut = throttle((e) => {
|
|
176
|
+
e.stopPropagation()
|
|
177
|
+
if (window.store.hasNodePty) {
|
|
178
|
+
window.store.addTab()
|
|
179
|
+
} else {
|
|
180
|
+
window.store.onNewSsh()
|
|
181
|
+
}
|
|
182
|
+
}, 500)
|
|
183
|
+
|
|
175
184
|
toggleAddBtnShortcut = throttle((e) => {
|
|
176
185
|
e.stopPropagation()
|
|
177
186
|
const { currentLayoutBatch } = window.store
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Modal,
|
|
3
2
|
Spin,
|
|
4
3
|
Button,
|
|
5
4
|
Empty
|
|
@@ -9,6 +8,7 @@ import * as ls from '../../common/safe-local-storage'
|
|
|
9
8
|
import {
|
|
10
9
|
sshConfigLoadKey
|
|
11
10
|
} from '../../common/constants'
|
|
11
|
+
import Modal from '../common/modal'
|
|
12
12
|
import { ReloadOutlined } from '@ant-design/icons'
|
|
13
13
|
import LoadSshConfigsItem from './load-ssh-configs-item'
|
|
14
14
|
import './ssh-config.styl'
|