@electerm/electerm-react 1.38.65 → 1.38.80

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.
Files changed (43) hide show
  1. package/client/common/constants.js +6 -3
  2. package/client/common/create-title.jsx +9 -1
  3. package/client/common/sftp.js +3 -0
  4. package/client/components/batch-op/batch-op.jsx +1 -6
  5. package/client/components/bookmark-form/bookmark-form.styl +3 -1
  6. package/client/components/bookmark-form/index.jsx +12 -8
  7. package/client/components/bookmark-form/render-ssh-tunnel.jsx +210 -88
  8. package/client/components/bookmark-form/ssh-form-ui.jsx +1 -1
  9. package/client/components/bookmark-form/web-form-ui.jsx +96 -0
  10. package/client/components/bookmark-form/web-form.jsx +16 -0
  11. package/client/components/main/main.jsx +14 -0
  12. package/client/components/quick-commands/qm.styl +1 -1
  13. package/client/components/session/session.styl +4 -1
  14. package/client/components/session/sessions.jsx +16 -2
  15. package/client/components/session/web-session.jsx +20 -0
  16. package/client/components/sftp/{confirm-modal.jsx → confirm-modal-store.jsx} +81 -50
  17. package/client/components/sftp/file-item.jsx +2 -0
  18. package/client/components/sftp/sftp-entry.jsx +27 -37
  19. package/client/components/sftp/transfer-conflict-store.jsx +291 -0
  20. package/client/components/sftp/transport-action-store.jsx +430 -0
  21. package/client/components/sftp/transports-action-store.jsx +102 -0
  22. package/client/components/sftp/transports-ui-store.jsx +30 -0
  23. package/client/components/sidebar/transfer-list-control.jsx +5 -14
  24. package/client/components/sidebar/transport-ui.jsx +2 -12
  25. package/client/components/tabs/tab.jsx +43 -2
  26. package/client/components/tabs/tabs.styl +1 -1
  27. package/client/components/terminal/index.jsx +14 -1
  28. package/client/components/terminal/terminal-interactive.jsx +15 -0
  29. package/client/components/terminal-info/disk.jsx +9 -0
  30. package/client/entry/worker.js +5 -1
  31. package/client/store/index.js +16 -1
  32. package/client/store/init-state.js +2 -3
  33. package/client/store/sync.js +5 -2
  34. package/client/store/tab.js +1 -1
  35. package/client/store/transfer-list.js +55 -2
  36. package/client/store/watch.js +0 -8
  37. package/package.json +1 -1
  38. package/client/components/sftp/transfer-conflict.jsx +0 -323
  39. package/client/components/sftp/transport-action.jsx +0 -412
  40. package/client/components/sftp/transport-entry.jsx +0 -108
  41. package/client/components/sftp/transport-types.js +0 -8
  42. package/client/components/sftp/transports-action.jsx +0 -111
  43. 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 = 5
41
+ export const maxTransport = 1
42
42
  export const maxSftpHistory = 20
43
43
  export const maxZoom = 8
44
44
  export const minZoom = 0.5
@@ -66,7 +66,8 @@ export const connectionMap = buildConst([
66
66
  'ssh',
67
67
  'telnet',
68
68
  'serial',
69
- 'local'
69
+ 'local',
70
+ 'web'
70
71
  ])
71
72
 
72
73
  export const authTypeMap = buildConst([
@@ -131,6 +132,7 @@ export const terminalSplitDirectionMap = buildConst([
131
132
  ])
132
133
 
133
134
  export const terminalSshConfigType = 'ssh-config'
135
+ export const terminalWebType = 'web'
134
136
  export const terminalSerialType = 'serial'
135
137
  export const terminalTelnetType = 'telnet'
136
138
  export const terminalLocalType = 'local'
@@ -278,7 +280,8 @@ export const commonActions = {
278
280
  closeContextMenu: 'close-context-menu',
279
281
  clickContextMenu: 'click-context-menu',
280
282
  openContextMenu: 'open-context-menu',
281
- addTransfer: 'add-transfer'
283
+ addTransfer: 'add-transfer',
284
+ sftpList: 'sftp-list'
282
285
  }
283
286
 
284
287
  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
  }
@@ -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
- postMsg({
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,4 +5,6 @@
5
5
  .mg60b
6
6
  margin-bottom 60px
7
7
  .compact-input input
8
- width 80px !important
8
+ width 80px !important
9
+ .ssh-tunnels-host
10
+ margin-bottom 0
@@ -9,6 +9,7 @@ import {
9
9
  settingMap,
10
10
  connectionMap,
11
11
  terminalSerialType,
12
+ terminalWebType,
12
13
  terminalLocalType,
13
14
  terminalTelnetType,
14
15
  newBookmarkIdPrefix
@@ -17,6 +18,7 @@ import SshForm from './ssh-form'
17
18
  import SerialForm from './serial-form'
18
19
  import LocalForm from './local-form'
19
20
  import TelnetForm from './telnet-form'
21
+ import WebForm from './web-form'
20
22
  import { createTitleWithTag } from '../../common/create-title'
21
23
  import {
22
24
  LoadingOutlined,
@@ -33,13 +35,14 @@ export default class BookmarkIndex extends Component {
33
35
  constructor (props) {
34
36
  super(props)
35
37
  let initType = props.formData.type
36
- if (initType === terminalTelnetType) {
37
- initType = terminalTelnetType
38
- } else if (initType === terminalSerialType) {
39
- initType = terminalSerialType
40
- } else if (initType === terminalLocalType) {
41
- initType = terminalLocalType
42
- } else {
38
+ if (
39
+ ![
40
+ terminalTelnetType,
41
+ terminalWebType,
42
+ terminalLocalType,
43
+ terminalSerialType
44
+ ].includes(initType)
45
+ ) {
43
46
  initType = connectionMap.ssh
44
47
  }
45
48
  this.state = {
@@ -64,7 +67,8 @@ export default class BookmarkIndex extends Component {
64
67
  [connectionMap.ssh]: SshForm,
65
68
  [connectionMap.telnet]: TelnetForm,
66
69
  [connectionMap.serial]: SerialForm,
67
- [connectionMap.local]: LocalForm
70
+ [connectionMap.local]: LocalForm,
71
+ [connectionMap.web]: WebForm
68
72
  }
69
73
 
70
74
  handleChange = (e) => {
@@ -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 { MinusCircleOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
11
- import { formItemLayout } from '../../common/form-layout'
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 renderSshTunnel () {
23
- function renderItem (field, i, add, remove) {
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
- <Space
26
- align='center'
27
- key={field.key}
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={[field.name, 'sshTunnel']}
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={e('localPort')}
80
- name={[field.name, 'sshTunnelLocalPort']}
81
- initialValue={22}
176
+ label={s('remote')}
177
+ {...formItemLayout}
82
178
  required
83
- className='mg2x'
179
+ className='ssh-tunnels-host'
84
180
  >
85
- <InputNumber
86
- min={1}
87
- max={65535}
88
- // addonBefore={e('localPort')}
89
- className='compact-input'
90
- placeholder={e('port')}
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
- <Button
94
- icon={<MinusCircleOutlined />}
95
- onClick={() => remove(field.name)}
96
- className='mg24b'
97
- />
98
- </Space>
99
- )
100
- }
101
-
102
- return [
103
- <FormList
104
- {...formItemLayout}
105
- label={e('sshTunnel')}
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
  }
@@ -65,7 +65,7 @@ export default function BookmarkFormUI (props) {
65
65
  encode: encodes[0],
66
66
  envLang: defaultEnvLang,
67
67
  enableSsh: true,
68
- sshTunnels: [{}],
68
+ sshTunnels: [],
69
69
  runScripts: [{}],
70
70
  category: initBookmarkGroupId,
71
71
  connectionHoppings: []
@@ -0,0 +1,96 @@
1
+ /**
2
+ * web form
3
+ */
4
+
5
+ import { useEffect } from 'react'
6
+ import {
7
+ Input,
8
+ Form
9
+ } from 'antd'
10
+ import { formItemLayout } from '../../common/form-layout'
11
+ import {
12
+ newBookmarkIdPrefix,
13
+ terminalWebType
14
+ } from '../../common/constants'
15
+ import useSubmit from './use-submit'
16
+ import copy from 'json-deep-copy'
17
+ import { defaults } from 'lodash-es'
18
+ import { ColorPickerItem } from './color-picker-item.jsx'
19
+ import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
20
+
21
+ const FormItem = Form.Item
22
+ const { prefix } = window
23
+ const e = prefix('form')
24
+
25
+ export default function LocalFormUi (props) {
26
+ const [
27
+ form,
28
+ handleFinish,
29
+ submitUi
30
+ ] = useSubmit(props)
31
+ useEffect(() => {
32
+ if (props.formData.id.startsWith(newBookmarkIdPrefix)) {
33
+ form.setFieldsValue({
34
+ category: props.currentBookmarkGroupId
35
+ })
36
+ }
37
+ }, [props.currentBookmarkGroupId])
38
+ let initialValues = copy(props.formData)
39
+ const defaultValues = {
40
+ type: terminalWebType,
41
+ color: getRandomDefaultColor()
42
+ }
43
+ initialValues = defaults(initialValues, defaultValues)
44
+ function renderCommon () {
45
+ return (
46
+ <div className='pd1x'>
47
+ <FormItem
48
+ {...formItemLayout}
49
+ label={e('title')}
50
+ hasFeedback
51
+ >
52
+ <FormItem noStyle name='title'>
53
+ <Input addonBefore={<ColorPickerItem />} />
54
+ </FormItem>
55
+ </FormItem>
56
+ <FormItem
57
+ {...formItemLayout}
58
+ label={e('URL')}
59
+ hasFeedback
60
+ name='url'
61
+ required
62
+ >
63
+ <Input addonBefore={<ColorPickerItem />} />
64
+ </FormItem>
65
+ <FormItem
66
+ {...formItemLayout}
67
+ label={e('description')}
68
+ name='description'
69
+ hasFeedback
70
+ >
71
+ <Input.TextArea rows={1} />
72
+ </FormItem>
73
+ <FormItem
74
+ {...formItemLayout}
75
+ label='type'
76
+ name='type'
77
+ className='hide'
78
+ >
79
+ <Input />
80
+ </FormItem>
81
+ </div>
82
+ )
83
+ }
84
+
85
+ return (
86
+ <Form
87
+ form={form}
88
+ onFinish={handleFinish}
89
+ initialValues={initialValues}
90
+ name='local-form'
91
+ >
92
+ {renderCommon()}
93
+ {submitUi}
94
+ </Form>
95
+ )
96
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * bookmark form
3
+ */
4
+ import BookmarkForm from './ssh-form'
5
+ import WebFormUi from './web-form-ui'
6
+
7
+ export default class WebForm extends BookmarkForm {
8
+ render () {
9
+ return (
10
+ <WebFormUi
11
+ {...this.props}
12
+ {...this.getProps()}
13
+ />
14
+ )
15
+ }
16
+ }
@@ -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
  )
@@ -8,7 +8,7 @@
8
8
  left 43px
9
9
  right 0
10
10
  color text
11
- z-index 200
11
+ z-index 89
12
12
  height auto
13
13
  bottom 36px
14
14
  background alpha(main-dark, .9)
@@ -55,4 +55,7 @@
55
55
  .no-sessions
56
56
  background main-dark
57
57
  text-align center
58
- padding 50px 0
58
+ padding 50px 0
59
+ .web-session-wrap
60
+ height 100vh
61
+ background main