@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
@@ -0,0 +1,102 @@
1
+ /**
2
+ * pass transfer list from props
3
+ * when list changes, do transfer and other op
4
+ */
5
+
6
+ import { Component } from '../common/react-subx'
7
+ import Transports from './transports-ui-store'
8
+ import { maxTransport } from '../../common/constants'
9
+
10
+ export default class TransportsActionStore extends Component {
11
+ componentDidMount () {
12
+ this.control()
13
+ }
14
+
15
+ componentDidUpdate (prevProps) {
16
+ if (
17
+ prevProps._fileTransfers !== this.props._fileTransfers
18
+ ) {
19
+ this.control()
20
+ }
21
+ }
22
+
23
+ control = async () => {
24
+ const { store } = this.props
25
+ let {
26
+ fileTransfers
27
+ } = store
28
+
29
+ fileTransfers = fileTransfers.map(t => {
30
+ const {
31
+ typeTo,
32
+ typeFrom,
33
+ fromFile,
34
+ inited
35
+ } = t
36
+ const ready = !!fromFile
37
+ if (typeTo === typeFrom && ready && !inited) {
38
+ t.inited = true
39
+ }
40
+ return t
41
+ })
42
+ // if (pauseAllTransfer) {
43
+ // return store.setFileTransfers(fileTransfers)
44
+ // }
45
+ let count = fileTransfers.filter(t => {
46
+ const {
47
+ typeTo,
48
+ typeFrom,
49
+ inited
50
+ } = t
51
+ return typeTo !== typeFrom && inited
52
+ }).length
53
+ if (count >= maxTransport) {
54
+ return store.setFileTransfers(fileTransfers)
55
+ }
56
+ const len = fileTransfers.length
57
+ const ids = []
58
+ for (let i = 0; i < len; i++) {
59
+ const tr = fileTransfers[i]
60
+ const {
61
+ typeTo,
62
+ typeFrom,
63
+ inited,
64
+ fromFile,
65
+ error,
66
+ id,
67
+ action
68
+ } = tr
69
+ if (!error) {
70
+ ids.push(id)
71
+ }
72
+ const isTransfer = typeTo !== typeFrom
73
+ const ready = (
74
+ action && fromFile
75
+ )
76
+ if (
77
+ !ready ||
78
+ inited ||
79
+ !isTransfer
80
+ ) {
81
+ continue
82
+ }
83
+ // if (isTransfer && tr.fromFile.isDirectory) {
84
+ // i = len
85
+ // continue
86
+ // }
87
+ if (
88
+ fromFile && count < maxTransport
89
+ ) {
90
+ count++
91
+ tr.inited = true
92
+ }
93
+ }
94
+ store.setFileTransfers(fileTransfers)
95
+ }
96
+
97
+ render () {
98
+ return (
99
+ <Transports {...this.props} />
100
+ )
101
+ }
102
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * transporter UI component
3
+ */
4
+ import { Component } from '../common/react-subx'
5
+ import Transport from './transport-action-store'
6
+
7
+ export default class TransportsUI extends Component {
8
+ render () {
9
+ const { store } = this.props
10
+ const {
11
+ fileTransfers
12
+ } = store
13
+ if (!fileTransfers.length) {
14
+ return null
15
+ }
16
+ return fileTransfers.map((t, i) => {
17
+ const { id } = t
18
+ return (
19
+ <Transport
20
+ {...this.props}
21
+ transfer={t}
22
+ inited={t.inited}
23
+ cancel={t.cancel}
24
+ pause={t.pausing}
25
+ key={id + ':tr:' + i}
26
+ />
27
+ )
28
+ })
29
+ }
30
+ }
@@ -8,10 +8,6 @@ import {
8
8
  PauseCircleOutlined
9
9
  } from '@ant-design/icons'
10
10
  import { get } from 'lodash-es'
11
- import {
12
- transportTypes
13
- } from '../sftp/transport-types'
14
- import postMessage from '../../common/post-msg'
15
11
 
16
12
  const { Option } = Select
17
13
 
@@ -28,21 +24,16 @@ export default class TransferModalUI extends Component {
28
24
  }
29
25
 
30
26
  handlePauseOrResumeAll = () => {
31
- postMessage({
32
- action: transportTypes.pauseOrResumeAll,
33
- id: this.state.filter
34
- })
27
+ const { store } = window
28
+ store.pauseAllTransfer ? store.resumeAll() : store.pauseAll()
35
29
  }
36
30
 
37
31
  handleCancelAll = () => {
38
- postMessage({
39
- action: transportTypes.cancelAll,
40
- id: this.state.filter
41
- })
32
+ window.store.cancelAll()
42
33
  }
43
34
 
44
35
  getGroups = () => {
45
- const fileTransfers = this.props.store.getTransfers()
36
+ const fileTransfers = this.props.store.fileTransfers
46
37
  const tree = fileTransfers.reduce((p, k) => {
47
38
  const {
48
39
  id,
@@ -79,7 +70,7 @@ export default class TransferModalUI extends Component {
79
70
  const {
80
71
  filter
81
72
  } = this.state
82
- const fileTransfers = this.props.store.getTransfers()
73
+ const fileTransfers = this.props.store.fileTransfers
83
74
  return filter === 'all'
84
75
  ? fileTransfers
85
76
  : fileTransfers.filter(d => d.sessionId === filter)
@@ -8,10 +8,6 @@ import {
8
8
  PlayCircleOutlined,
9
9
  PauseCircleOutlined
10
10
  } from '@ant-design/icons'
11
- import {
12
- transportTypes
13
- } from '../sftp/transport-types'
14
- import postMessage from '../../common/post-msg'
15
11
  import './transfer.styl'
16
12
 
17
13
  const { prefix } = window
@@ -35,16 +31,10 @@ export default function Transporter (props) {
35
31
  id
36
32
  } = props.transfer
37
33
  function cancel () {
38
- postMessage({
39
- action: transportTypes.cancelTransport,
40
- id
41
- })
34
+ window.store.cancelTransfer(id)
42
35
  }
43
36
  function handlePauseOrResume () {
44
- postMessage({
45
- action: transportTypes.pauseOrResumeTransfer,
46
- id
47
- })
37
+ window.store.toggleTransfer(id)
48
38
  }
49
39
  const isTransfer = typeTo !== typeFrom
50
40
  const Icon = !pausing ? PauseCircleOutlined : PlayCircleOutlined
@@ -26,6 +26,7 @@ import { shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
26
26
  const { prefix } = window
27
27
  const e = prefix('tabs')
28
28
  const m = prefix('menu')
29
+ const s = prefix('sftp')
29
30
  const onDragCls = 'ondrag-tab'
30
31
  const onDragOverCls = 'dragover-tab'
31
32
 
@@ -346,6 +347,36 @@ class Tab extends Component {
346
347
  )
347
348
  }
348
349
 
350
+ // sshTunnelResults is a array of { sshTunnel, error? }, sshTunnel is a object has props of sshTunnelLocalPort, sshTunnelRemoteHost, sshTunnelRemotePort, sshTunnel, sshTunnelLocalHost, should build sshTunnel string from sshTunnel object, when error exist, this string should start with "error:", return title and sshTunnelResults list in react element.
351
+ renderTitle = (sshTunnelResults, title) => {
352
+ const list = sshTunnelResults.map(({ sshTunnel: obj, error }, i) => {
353
+ const {
354
+ sshTunnelLocalPort,
355
+ sshTunnelRemoteHost = '127.0.0.1',
356
+ sshTunnelRemotePort,
357
+ sshTunnel,
358
+ sshTunnelLocalHost = '127.0.0.1',
359
+ name
360
+ } = obj
361
+ let tunnel = sshTunnel === 'forwardRemoteToLocal'
362
+ ? `-> ${s('remote')}:${sshTunnelRemoteHost}:${sshTunnelRemotePort} -> ${sshTunnelLocalHost}:${sshTunnelLocalPort}`
363
+ : `-> ${s('local')}:${sshTunnelLocalHost}:${sshTunnelLocalPort} -> ${sshTunnelRemoteHost}:${sshTunnelRemotePort}`
364
+ if (error) {
365
+ tunnel = `error: ${tunnel}`
366
+ }
367
+ if (name) {
368
+ tunnel = `[${name}] ${tunnel}`
369
+ }
370
+ return <div key={tunnel}>{tunnel}</div>
371
+ })
372
+ return (
373
+ <div>
374
+ <div>${title}</div>
375
+ {list}
376
+ </div>
377
+ )
378
+ }
379
+
349
380
  renderCloseIcon () {
350
381
  return (
351
382
  <span className='tab-close pointer'>
@@ -360,7 +391,13 @@ class Tab extends Component {
360
391
  } = this.props
361
392
  const { isLast } = this.props
362
393
  const { tab, terminalOnData } = this.state
363
- const { id, isEditting, status, isTransporting } = tab
394
+ const {
395
+ id,
396
+ isEditting,
397
+ status,
398
+ isTransporting,
399
+ sshTunnelResults
400
+ } = tab
364
401
  const active = id === currentTabId
365
402
  const cls = classnames(
366
403
  `tab-${id}`,
@@ -378,6 +415,10 @@ class Tab extends Component {
378
415
  }
379
416
  )
380
417
  const title = createName(tab)
418
+ let tooltipTitle = title
419
+ if (sshTunnelResults) {
420
+ tooltipTitle = this.renderTitle(sshTunnelResults, title)
421
+ }
381
422
  if (isEditting) {
382
423
  return this.renderEditting(tab, cls)
383
424
  }
@@ -387,7 +428,7 @@ class Tab extends Component {
387
428
  : {}
388
429
  return (
389
430
  <Tooltip
390
- title={title}
431
+ title={tooltipTitle}
391
432
  placement='top'
392
433
  >
393
434
  <div
@@ -151,7 +151,7 @@
151
151
  position absolute
152
152
  right 0
153
153
  top 0
154
- z-index 200
154
+ z-index 900
155
155
  border-radius 0 0 3px 3px
156
156
  padding 0
157
157
  background main-light
@@ -138,9 +138,13 @@ class Term extends Component {
138
138
  clearTimeout(this.timers[k])
139
139
  })
140
140
  this.onClose = true
141
- this.socket && this.socket.close()
141
+ if (this.socket) {
142
+ this.socket.close()
143
+ delete this.socket
144
+ }
142
145
  if (this.term) {
143
146
  this.term.dispose()
147
+ delete this.term
144
148
  }
145
149
  window.removeEventListener(
146
150
  'resize',
@@ -149,6 +153,13 @@ class Term extends Component {
149
153
  window.removeEventListener('message', this.handleEvent)
150
154
  this.dom.removeEventListener('contextmenu', this.onContextMenu)
151
155
  window.removeEventListener('message', this.onContextAction)
156
+ delete this.dom
157
+ delete this.attachAddon
158
+ delete this.fitAddon
159
+ delete this.zmodemAddon
160
+ delete this.searchAddon
161
+ delete this.serializeAddon
162
+ delete this.fitAddon
152
163
  }
153
164
 
154
165
  terminalConfigProps = [
@@ -1110,6 +1121,7 @@ class Term extends Component {
1110
1121
  ]),
1111
1122
  sessionId,
1112
1123
  tabId: id,
1124
+ srcTabId: tab.id,
1113
1125
  terminalIndex,
1114
1126
  termType,
1115
1127
  readyTimeout: config.sshReadyTimeout,
@@ -1225,6 +1237,7 @@ class Term extends Component {
1225
1237
  this.socketCloseWarning = notification.warning({
1226
1238
  key,
1227
1239
  message: e('socketCloseTip'),
1240
+ duration: 30,
1228
1241
  description: (
1229
1242
  <div className='pd2y'>
1230
1243
  <Button
@@ -5,7 +5,9 @@
5
5
  import { useEffect, useState } from 'react'
6
6
  import { Modal, Form, Button } from 'antd'
7
7
  import InputAutoFocus from '../common/input-auto-focus'
8
+ import { tabActions } from '../../common/constants'
8
9
  import wait from '../../common/wait'
10
+ import postMsg from '../../common/post-msg'
9
11
 
10
12
  const { prefix } = window
11
13
  const e = prefix('sftp')
@@ -16,6 +18,13 @@ export default function TermInteractive () {
16
18
  const [trigger] = useState(0)
17
19
  const [opts, setter] = useState(null)
18
20
  const [form] = Form.useForm()
21
+ function updateTab (data) {
22
+ postMsg({
23
+ action: tabActions.updateTabs,
24
+ id: data.tabId,
25
+ update: data.update
26
+ })
27
+ }
19
28
  function onMsg (e) {
20
29
  if (
21
30
  e &&
@@ -24,6 +33,12 @@ export default function TermInteractive () {
24
33
  e.data.includes('session-interactive')
25
34
  ) {
26
35
  setter(JSON.parse(e.data))
36
+ } else if (
37
+ e &&
38
+ e.data &&
39
+ e.data.includes('ssh-tunnel-result')
40
+ ) {
41
+ updateTab(JSON.parse(e.data))
27
42
  }
28
43
  }
29
44
  function clear () {
@@ -13,6 +13,15 @@ export default function TerminalInfoDisk (props) {
13
13
  return null
14
14
  }
15
15
  const col = colsParser(disks[0])
16
+ disks.sort((a, b) => {
17
+ if (a.filesystem.startsWith('/') && !b.filesystem.startsWith('/')) {
18
+ return -1
19
+ }
20
+ if (!a.filesystem.startsWith('/') && b.filesystem.startsWith('/')) {
21
+ return 1
22
+ }
23
+ return 0
24
+ })
16
25
  const ps = {
17
26
  rowKey: (rec) => `${rec.mount}_${rec.filesystem}`,
18
27
  dataSource: disks,
@@ -16,7 +16,11 @@ function createWs (
16
16
  const wsUrl = `ws://${host}:${port}/${type}/${id}?sessionId=${sessionId}&sftpId=${sftpId}&token=${tokenElecterm}`
17
17
  const ws = new WebSocket(wsUrl)
18
18
  ws.s = msg => {
19
- ws.send(JSON.stringify(msg))
19
+ try {
20
+ ws.send(JSON.stringify(msg))
21
+ } catch (e) {
22
+ console.error('ws send error', e)
23
+ }
20
24
  }
21
25
  ws.id = id
22
26
  ws.once = (callack, id) => {
@@ -63,7 +63,18 @@ function expandShorthandColor (color) {
63
63
  if (color.length === 4) {
64
64
  return '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]
65
65
  }
66
- return color
66
+ if (color.length === 7) {
67
+ return color
68
+ }
69
+ if (color.length < 7) {
70
+ return expandShorthandColor(color + 'f')
71
+ }
72
+ if (color.length > 7) {
73
+ return expandShorthandColor(color.slice(0, 7))
74
+ }
75
+ if (!/^#[A-Fa-f0-9]{6}$/.test(color)) {
76
+ return '#141314'
77
+ }
67
78
  }
68
79
 
69
80
  function isColorDark (_color) {
@@ -233,6 +244,10 @@ class Store {
233
244
  return JSON.parse(window.store._upgradeInfo)
234
245
  }
235
246
 
247
+ get transferToConfirm () {
248
+ return JSON.parse(window.store._transferToConfirm)
249
+ }
250
+
236
251
  get settingItem () {
237
252
  return JSON.parse(window.store._settingItem)
238
253
  }
@@ -92,9 +92,11 @@ export default () => {
92
92
 
93
93
  // sftp
94
94
  fileOperation: fileOperationsMap.cp, // cp or mv
95
+ pauseAllTransfer: false,
95
96
  transferTab: 'transfer',
96
97
  _transferHistory: '[]',
97
98
  _fileTransfers: '[]',
99
+ _transferToConfirm: '{}',
98
100
  _sftpSortSetting: ls.getItem(sftpDefaultSortSettingKey) || JSON.stringify({
99
101
  local: {
100
102
  prop: 'modifyTime',
@@ -169,9 +171,6 @@ export default () => {
169
171
  _serials: '[]',
170
172
  loaddingSerials: false,
171
173
 
172
- // transfer list
173
- transports: [],
174
-
175
174
  _sshConfigItems: '[]',
176
175
 
177
176
  appPath: '',
@@ -12,6 +12,7 @@ import fetch from '../common/fetch-from-server'
12
12
  import download from '../common/download'
13
13
  import { fixBookmarks } from '../common/db-fix'
14
14
  import dayjs from 'dayjs'
15
+ import parseJsonSafe from '../common/parse-json-safe'
15
16
 
16
17
  const names = without(dbNames, settingMap.history)
17
18
  const {
@@ -262,10 +263,12 @@ export default (Store) => {
262
263
  })
263
264
  store.setItems(n, arr)
264
265
  }
265
- const userConfig = JSON.parse(
266
+ const userConfig = parseJsonSafe(
266
267
  get(gist, 'files["userConfig.json"].content')
267
268
  )
268
- store.setTheme(userConfig.theme)
269
+ if (userConfig && userConfig.theme) {
270
+ store.setTheme(userConfig.theme)
271
+ }
269
272
  const up = {
270
273
  [type + 'LastSyncTime']: Date.now()
271
274
  }
@@ -11,7 +11,7 @@ import postMsg from '../common/post-msg'
11
11
  export default Store => {
12
12
  Store.prototype.updateTabsStatus = function () {
13
13
  const tabIds = uniq(
14
- window.store.getTransfers().map(d => d.tabId)
14
+ window.store.fileTransfers.map(d => d.tabId)
15
15
  )
16
16
  postMsg({
17
17
  action: tabActions.updateTabsStatus,
@@ -4,10 +4,9 @@ export default Store => {
4
4
  window.store.transferTab = tab
5
5
  }
6
6
 
7
- // should update any item with same id in oldList from list array, should add any new item from list array to oldList, should remove any item with same id and sessionId in oldList but not in list array
8
7
  Store.prototype.setTransfers = function (list, _sessId) {
9
8
  const { store } = window
10
- let oldList = store.getTransfers()
9
+ let oldList = store.fileTransfers
11
10
  const sessId = _sessId || list[0].sessionId
12
11
  const arr2 = oldList.filter(t => {
13
12
  return t.sessionId === sessId
@@ -48,4 +47,58 @@ export default Store => {
48
47
  Store.prototype.addTransfers = function (objs) {
49
48
  return window.store.addItems(objs, 'fileTransfers')
50
49
  }
50
+ Store.prototype.setFileTransfers = function (objs) {
51
+ return window.store.setState('fileTransfers', objs)
52
+ }
53
+ Store.prototype.addTransferList = function (objs) {
54
+ const { store } = window
55
+ store.setFileTransfers([
56
+ ...store.fileTransfers,
57
+ ...objs
58
+ ])
59
+ }
60
+ Store.prototype.toggleTransfer = function (itemId) {
61
+ const { store } = window
62
+ const { fileTransfers } = store
63
+ const index = findIndex(fileTransfers, t => t.id === itemId)
64
+ if (index < 0) {
65
+ return
66
+ }
67
+ fileTransfers[index].pausing = !fileTransfers[index].pausing
68
+ store.setFileTransfers(fileTransfers)
69
+ }
70
+
71
+ Store.prototype.pauseAll = function () {
72
+ const { store } = window
73
+ store.pauseAllTransfer = true
74
+ store.setFileTransfers(store.fileTransfers.map(t => {
75
+ t.pausing = true
76
+ return t
77
+ }))
78
+ }
79
+ Store.prototype.resumeAll = function () {
80
+ const { store } = window
81
+ store.pauseAllTransfer = false
82
+ store.setFileTransfers(store.fileTransfers.map(t => {
83
+ t.pausing = false
84
+ return t
85
+ }))
86
+ }
87
+ Store.prototype.cancelAll = function () {
88
+ const { store } = window
89
+ store.setFileTransfers(store.fileTransfers.map(t => {
90
+ t.cancel = true
91
+ return t
92
+ }))
93
+ }
94
+ Store.prototype.cancelTransfer = function (itemId) {
95
+ const { store } = window
96
+ const { fileTransfers } = store
97
+ const index = findIndex(fileTransfers, t => t.id === itemId)
98
+ if (index < 0) {
99
+ return
100
+ }
101
+ fileTransfers[index].cancel = true
102
+ store.setFileTransfers(fileTransfers)
103
+ }
51
104
  }
@@ -33,14 +33,6 @@ export default store => {
33
33
  // return store.menuOpened
34
34
  // })
35
35
 
36
- // autoRun(store, () => {
37
- // const v = store.getItems('tabs').map(t => {
38
- // delete t.isTransporting
39
- // return t
40
- // })
41
- // update('sessions', v)
42
- // return store._tabs
43
- // }, func => debounce(func, 100))
44
36
  for (const name of dbNamesForWatch) {
45
37
  autoRun(store, async () => {
46
38
  await update(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.38.65",
3
+ "version": "1.38.80",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",