@electerm/electerm-react 3.3.8 → 3.6.6

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.
@@ -229,11 +229,6 @@ export const rendererTypes = {
229
229
  canvas: 'canvas',
230
230
  webGL: 'webGL'
231
231
  }
232
- export const mirrors = {
233
- 'download-electerm': 'download-electerm',
234
- github: 'github',
235
- sourceforge: 'sourceforge'
236
- }
237
232
  export const downloadUpgradeTimeout = 20000
238
233
  export const expandedKeysLsKey = 'expanded-keys'
239
234
  export const resolutionsLsKey = 'custom-resolution-key'
@@ -16,6 +16,90 @@ const fs = fsFunctions.reduce((prev, func) => {
16
16
  return prev
17
17
  }, {})
18
18
 
19
+ // Encoding function
20
+ fs.encodeUint8Array = (uint8Array) => {
21
+ let str = ''
22
+ const len = uint8Array.byteLength
23
+
24
+ for (let i = 0; i < len; i++) {
25
+ str += String.fromCharCode(uint8Array[i])
26
+ }
27
+
28
+ return btoa(str)
29
+ }
30
+
31
+ // Decoding function
32
+ fs.decodeBase64String = (base64String) => {
33
+ const str = atob(base64String)
34
+ const len = str.length
35
+
36
+ const uint8Array = new Uint8Array(len)
37
+
38
+ for (let i = 0; i < len; i++) {
39
+ uint8Array[i] = str.charCodeAt(i)
40
+ }
41
+
42
+ return uint8Array
43
+ }
44
+
45
+ Object.assign(fs, {
46
+ stat: (path, cb) => {
47
+ window.fs.statCustom(path)
48
+ .catch(err => cb(err))
49
+ .then(obj => {
50
+ obj.isDirectory = () => obj.isD
51
+ obj.isFile = () => obj.isF
52
+ cb(undefined, obj)
53
+ })
54
+ },
55
+ access: (...args) => {
56
+ const cb = args.pop()
57
+ window.fs.access(...args)
58
+ .then((data) => cb(undefined, data))
59
+ .catch((err) => cb(err))
60
+ },
61
+ open: (...args) => {
62
+ const cb = args.pop()
63
+ window.fs.openCustom(...args)
64
+ .then((data) => cb(undefined, data))
65
+ .catch((err) => cb(err))
66
+ },
67
+ read: (p1, arr, ...args) => {
68
+ const cb = args.pop()
69
+ window.fs.readCustom(
70
+ p1,
71
+ arr.length,
72
+ ...args
73
+ )
74
+ .then((data) => {
75
+ const { n, newArr } = data
76
+ const newArr1 = window.fs.decodeBase64String(newArr)
77
+ cb(undefined, n, newArr1)
78
+ })
79
+ .catch(err => cb(err))
80
+ },
81
+ close: (fd, cb) => {
82
+ window.fs.closeCustom(fd)
83
+ .then((data) => cb(undefined, data))
84
+ .catch((err) => cb(err))
85
+ },
86
+ readdir: (p, cb) => {
87
+ window.fs.readdir(p)
88
+ .then((data) => cb(undefined, data))
89
+ .catch((err) => cb(err))
90
+ },
91
+ write: (p1, buf, cb) => {
92
+ window.fs.writeCustom(p1, window.fs.encodeUint8Array(buf))
93
+ .then((data) => cb(undefined, data))
94
+ .catch((err) => cb(err))
95
+ },
96
+ realpath: (p, cb) => {
97
+ window.fs.realpath(p)
98
+ .then((data) => cb(undefined, data))
99
+ .catch((err) => cb(err))
100
+ }
101
+ })
102
+
19
103
  window.fs = fs
20
104
 
21
105
  export default fs
@@ -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 = copy(this.onceIds)
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
- if (action === 'close') {
120
- wss[id].close && wss[id].close()
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)
@@ -1,34 +1,15 @@
1
- import React, { useState } from 'react'
2
- import { Alert, Button } from 'antd'
1
+ import React from 'react'
2
+ import { Alert } from 'antd'
3
3
  import ExternalLink from '../common/external-link'
4
4
 
5
5
  const batchOpWikiLink = 'https://github.com/electerm/electerm/wiki/batch-operation'
6
6
 
7
7
  export default function BatchOpAlert () {
8
- const [expanded, setExpanded] = useState(false)
9
-
10
8
  const description = (
11
- <div>
12
- <div>
13
- <p>Actions: <code>connect, command, sftp_upload, sftp_download</code></p>
14
- <p><ExternalLink to={batchOpWikiLink}>{batchOpWikiLink}</ExternalLink></p>
15
- </div>
16
- {expanded && (
17
- <div className='mg1t'>
18
- <p><strong>connect</strong> params: <code>host, port, username, authType, password, privateKey, passphrase, certificate, profile, enableSftp, enableSsh, useSshAgent, sshAgent, term, encode, envLang, setEnv, startDirectoryRemote, startDirectoryLocal, proxy, x11, displayRaw, sshTunnels, connectionHoppings</code></p>
19
- <p><strong>command</strong> params: <code>command</code></p>
20
- <p><strong>sftp_upload</strong> params: <code>localPath, remotePath</code></p>
21
- <p><strong>sftp_download</strong> params: <code>remotePath, localPath</code></p>
22
- </div>
23
- )}
24
- <Button
25
- size='small'
26
- className='mg1y'
27
- onClick={() => setExpanded(v => !v)}
28
- >
29
- {expanded ? 'Show less' : 'Show more'}
30
- </Button>
31
- </div>
9
+ <>
10
+ <p>Actions: <code>connect, command, sftp_upload, sftp_download</code></p>
11
+ <div><ExternalLink to={batchOpWikiLink}>{batchOpWikiLink}</ExternalLink></div>
12
+ </>
32
13
  )
33
14
 
34
15
  return (
@@ -15,7 +15,9 @@ import message from '../common/message'
15
15
  import { refsStatic } from '../common/ref'
16
16
  import generate from '../../common/uid'
17
17
  import fs from '../../common/fs'
18
+ import { safeGetItem, safeSetItem } from '../../common/safe-local-storage'
18
19
 
20
+ const batchOpEditorKey = 'batch-op-editor-content'
19
21
  const workflowExample = `[
20
22
  {
21
23
  "name": "Connect SSH",
@@ -80,11 +82,9 @@ const workflowExample = `[
80
82
  ]`
81
83
 
82
84
  function getDefaultValue (widget) {
83
- if (widget?.info?.configs) {
84
- const config = widget.info.configs.find(c => c.name === 'workflowJson')
85
- if (config?.default) return config.default
86
- }
87
- return ''
85
+ const saved = safeGetItem(batchOpEditorKey)
86
+ if (saved) return saved
87
+ return workflowExample
88
88
  }
89
89
 
90
90
  export default function BatchOpEditor ({ widget }) {
@@ -96,6 +96,10 @@ export default function BatchOpEditor ({ widget }) {
96
96
  if (v) setValue(v)
97
97
  }, [widget?.id])
98
98
 
99
+ useEffect(() => {
100
+ safeSetItem(batchOpEditorKey, value)
101
+ }, [value])
102
+
99
103
  const handleExecute = async () => {
100
104
  if (!value || executing) return
101
105
  setExecuting(true)
@@ -22,6 +22,7 @@ import SshHostSelector from './ssh-host-selector.jsx'
22
22
  import SshAuthTypeSelector from './ssh-auth-type-selector.jsx'
23
23
  import SshAuthSelector from './ssh-auth-selector.jsx'
24
24
  import CategorySelect from './category-select.jsx'
25
+ import ExternalLink from '../../common/external-link.jsx'
25
26
  const Fragment = React.Fragment
26
27
  const FormItem = Form.Item
27
28
 
@@ -115,6 +116,20 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
115
116
 
116
117
  // Render complex/custom components directly (no extra wrapper component)
117
118
  switch (type) {
119
+ case 'wiki':
120
+ return (
121
+ <Alert
122
+ key={name}
123
+ type='warning'
124
+ className='mg2b'
125
+ showIcon
126
+ description={
127
+ <>
128
+ <ExternalLink to={item.link}>{item.link}</ExternalLink>
129
+ </>
130
+ }
131
+ />
132
+ )
118
133
  case 'alert':
119
134
  return <Alert key={name} {...item.props} />
120
135
  case 'info':
@@ -21,6 +21,11 @@ const rdpConfig = {
21
21
  key: 'auth',
22
22
  label: e('auth'),
23
23
  fields: [
24
+ {
25
+ type: 'wiki',
26
+ name: 'rdp-limitation-warning',
27
+ link: 'https://github.com/electerm/electerm/wiki/RDP-limitation'
28
+ },
24
29
  commonFields.category,
25
30
  commonFields.colorTitle,
26
31
  { type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
@@ -1,6 +1,6 @@
1
1
  import { PureComponent } from 'react'
2
2
  import { CloseOutlined, MinusSquareOutlined, UpCircleOutlined } from '@ant-design/icons'
3
- import { Button } from 'antd'
3
+ import { Button, Select, Space } from 'antd'
4
4
  import { getLatestReleaseInfo, getLatestReleaseVersion } from '../../common/update-check'
5
5
  import upgrade from '../../common/upgrade'
6
6
  import compare from '../../common/version-compare'
@@ -9,8 +9,7 @@ import {
9
9
  isMac,
10
10
  isWin,
11
11
  packInfo,
12
- downloadUpgradeTimeout,
13
- mirrors
12
+ downloadUpgradeTimeout
14
13
  } from '../../common/constants'
15
14
  import { checkSkipSrc } from '../../common/check-skip-src'
16
15
  import { debounce } from 'lodash-es'
@@ -18,6 +17,7 @@ import newTerm from '../../common/new-terminal'
18
17
  import Markdown from '../common/markdown'
19
18
  import downloadMirrors from '../../common/download-mirrors'
20
19
  import { refsStatic } from '../common/ref'
20
+ import message from '../common/message'
21
21
  import './upgrade.styl'
22
22
 
23
23
  const e = window.translate
@@ -25,10 +25,15 @@ const {
25
25
  homepage
26
26
  } = packInfo
27
27
 
28
+ const downloadMirrorList = [
29
+ 'github',
30
+ 'gh-proxy',
31
+ 'sourceforge'
32
+ ]
33
+
28
34
  export default class Upgrade extends PureComponent {
29
35
  state = {
30
- showCount: 0,
31
- mirror: mirrors.github
36
+ mirror: downloadMirrorList[1]
32
37
  }
33
38
 
34
39
  downloadTimer = null
@@ -39,15 +44,23 @@ export default class Upgrade extends PureComponent {
39
44
  }
40
45
  this.id = 'upgrade'
41
46
  refsStatic.add(this.id, this)
47
+ this.cleanupTimer = setInterval(() => {
48
+ const { noUpdateMessageExpires } = window.store.upgradeInfo
49
+ if (noUpdateMessageExpires && Date.now() > noUpdateMessageExpires) {
50
+ window.store.upgradeInfo.noUpdateMessage = ''
51
+ window.store.upgradeInfo.noUpdateMessageExpires = 0
52
+ }
53
+ }, 1000)
42
54
  }
43
55
 
44
- appUpdateCheck = (noSkip) => {
45
- this.setState(old => {
46
- return {
47
- showCount: old.showCount + 1
48
- }
49
- })
50
- this.getLatestRelease(noSkip)
56
+ componentWillUnmount () {
57
+ if (this.cleanupTimer) {
58
+ clearInterval(this.cleanupTimer)
59
+ }
60
+ }
61
+
62
+ appUpdateCheck = (isManual) => {
63
+ this.getLatestRelease(isManual)
51
64
  }
52
65
 
53
66
  changeProps = (update) => {
@@ -67,6 +80,12 @@ export default class Upgrade extends PureComponent {
67
80
  window.store.upgradeInfo = {}
68
81
  }
69
82
 
83
+ handleMirrorChange = (mirror) => {
84
+ this.setState({
85
+ mirror
86
+ })
87
+ }
88
+
70
89
  onData = (upgradePercent) => {
71
90
  clearTimeout(this.downloadTimer)
72
91
  if (upgradePercent >= 100) {
@@ -93,17 +112,8 @@ export default class Upgrade extends PureComponent {
93
112
  }
94
113
 
95
114
  timeout = () => {
96
- const { mirror } = this.state
97
115
  this.cancel()
98
- const next = mirror !== mirrors.sourceforge
99
- ? this.doUpgrade
100
- : undefined
101
- const nextMirror = mirror === mirrors['download-electerm']
102
- ? mirrors.sourceforge
103
- : mirrors['download-electerm']
104
- this.setState({
105
- mirror: nextMirror
106
- }, next)
116
+ message.error('Download timeout, please try again')
107
117
  }
108
118
 
109
119
  onEnd = () => {
@@ -146,7 +156,7 @@ export default class Upgrade extends PureComponent {
146
156
  this.handleClose()
147
157
  }
148
158
 
149
- getLatestRelease = async (noSkipVersion = false) => {
159
+ getLatestRelease = async (isManual = false) => {
150
160
  const { installSrc } = this.props
151
161
  if (checkSkipSrc(installSrc)) {
152
162
  return
@@ -167,10 +177,19 @@ export default class Upgrade extends PureComponent {
167
177
  const { skipVersion = 'v0.0.0' } = this.props
168
178
  const currentVer = 'v' + window.et.version.split('-')[0]
169
179
  const latestVer = releaseVer.tag_name
170
- if (!noSkipVersion && compare(skipVersion, latestVer) >= 0) {
180
+ if (!isManual && compare(skipVersion, latestVer) >= 0) {
171
181
  return
172
182
  }
173
183
  const shouldUpgrade = compare(currentVer, latestVer) < 0
184
+ if (!shouldUpgrade) {
185
+ if (isManual) {
186
+ this.changeProps({
187
+ noUpdateMessage: e('noNeed'),
188
+ noUpdateMessageExpires: Date.now() + 3000
189
+ })
190
+ }
191
+ return
192
+ }
174
193
  const canAutoUpgrade = installSrc || isWin || isMac
175
194
  let releaseInfo
176
195
  if (canAutoUpgrade) {
@@ -208,28 +227,6 @@ export default class Upgrade extends PureComponent {
208
227
  )
209
228
  }
210
229
 
211
- renderCanNotUpgrade = () => {
212
- const {
213
- showUpgradeModal
214
- } = this.props.upgradeInfo
215
- const cls = `animate upgrade-panel${showUpgradeModal ? '' : ' upgrade-panel-hide'}`
216
- return (
217
- <div className={cls}>
218
- <div className='upgrade-panel-title fix'>
219
- <span className='fleft'>
220
- {e('noNeed')}
221
- </span>
222
- <span className='fright'>
223
- <CloseOutlined className='pointer font16 close-upgrade-panel' onClick={this.handleClose} />
224
- </span>
225
- </div>
226
- <div className='upgrade-panel-body'>
227
- {e('noNeedDesc')}
228
- </div>
229
- </div>
230
- )
231
- }
232
-
233
230
  renderChangeLog = () => {
234
231
  const {
235
232
  releaseInfo
@@ -261,38 +258,8 @@ export default class Upgrade extends PureComponent {
261
258
  )
262
259
  }
263
260
 
264
- render () {
265
- const {
266
- showCount
267
- } = this.state
268
- const { installSrc } = this.props
269
- const {
270
- remoteVersion,
271
- upgrading,
272
- checkingRemoteVersion,
273
- showUpgradeModal,
274
- upgradePercent,
275
- shouldUpgrade,
276
- releaseInfo,
277
- error
278
- } = this.props.upgradeInfo
279
- if (error) {
280
- return this.renderError(error)
281
- }
282
- if (!shouldUpgrade && showCount < 2) {
283
- return null
284
- }
285
- if (!shouldUpgrade && showCount > 1) {
286
- return this.renderCanNotUpgrade()
287
- }
288
- if (checkingRemoteVersion) {
289
- return null
290
- }
291
- const cls = `animate upgrade-panel${showUpgradeModal ? '' : ' upgrade-panel-hide'}`
292
- const func = upgrading
293
- ? this.cancel
294
- : this.doUpgrade
295
- const getLink = (
261
+ renderLinks = () => {
262
+ return (
296
263
  <div>
297
264
  <p>
298
265
  {e('manuallyDownloadFrom')}:
@@ -307,7 +274,80 @@ export default class Upgrade extends PureComponent {
307
274
  {this.renderChangeLog()}
308
275
  </div>
309
276
  )
277
+ }
278
+
279
+ renderMirrorSelector = () => {
280
+ return (
281
+ <Select
282
+ value={this.state.mirror}
283
+ onChange={this.handleMirrorChange}
284
+ getPopupContainer={() => document.body}
285
+ size='small'
286
+ style={{ height: 32 }}
287
+ >
288
+ {downloadMirrorList.map((opt) => (
289
+ <Select.Option key={opt} value={opt}>{opt}</Select.Option>
290
+ ))}
291
+ </Select>
292
+ )
293
+ }
294
+
295
+ renderUpgradeButton = () => {
296
+ const { upgrading, upgradePercent, checkingRemoteVersion } = this.props.upgradeInfo
297
+ if (upgrading) {
298
+ const percent = upgradePercent || 0
299
+ return (
300
+ <Button
301
+ type='primary'
302
+ icon={<UpCircleOutlined />}
303
+ loading={checkingRemoteVersion}
304
+ disabled={checkingRemoteVersion}
305
+ onClick={() => this.cancel()}
306
+ className='mg1b'
307
+ >
308
+ <span>{`${e('upgrading')}... ${percent}% ${e('cancel')}`}</span>
309
+ </Button>
310
+ )
311
+ }
312
+ return (
313
+ <Space.Compact>
314
+ {this.renderMirrorSelector()}
315
+ <Button
316
+ type='primary'
317
+ icon={<UpCircleOutlined />}
318
+ loading={checkingRemoteVersion}
319
+ disabled={checkingRemoteVersion}
320
+ onClick={() => this.doUpgrade()}
321
+ className='mg1b'
322
+ >
323
+ {e('upgrade')}
324
+ </Button>
325
+ </Space.Compact>
326
+ )
327
+ }
328
+
329
+ renderUpgradeContent = () => {
330
+ const { installSrc } = this.props
310
331
  const skip = checkSkipSrc(installSrc)
332
+ if (skip) {
333
+ return this.renderLinks()
334
+ }
335
+ return (
336
+ <div>
337
+ {this.renderUpgradeButton()}
338
+ {this.renderSkipVersion()}
339
+ <div className='pd1t'>
340
+ {this.renderLinks()}
341
+ </div>
342
+ </div>
343
+ )
344
+ }
345
+
346
+ renderUpgradePanel = () => {
347
+ const { remoteVersion, releaseInfo, showUpgradeModal } = this.props.upgradeInfo
348
+ const cls = showUpgradeModal
349
+ ? 'animate upgrade-panel'
350
+ : 'animate upgrade-panel upgrade-panel-hide'
311
351
  return (
312
352
  <div className={cls}>
313
353
  <div className='upgrade-panel-title fix'>
@@ -319,34 +359,23 @@ export default class Upgrade extends PureComponent {
319
359
  </span>
320
360
  </div>
321
361
  <div className='upgrade-panel-body'>
322
- {
323
- !skip
324
- ? (
325
- <div>
326
- <Button
327
- type='primary'
328
- icon={<UpCircleOutlined />}
329
- loading={checkingRemoteVersion}
330
- disabled={checkingRemoteVersion}
331
- onClick={func}
332
- className='mg1b'
333
- >
334
- {
335
- upgrading
336
- ? <span>{`${e('upgrading')}... ${upgradePercent || 0}% ${e('cancel')}`}</span>
337
- : e('upgrade')
338
- }
339
- </Button>
340
- {this.renderSkipVersion()}
341
- <div className='pd1t'>
342
- {getLink}
343
- </div>
344
- </div>
345
- )
346
- : getLink
347
- }
362
+ {this.renderUpgradeContent()}
348
363
  </div>
349
364
  </div>
350
365
  )
351
366
  }
367
+
368
+ render () {
369
+ const { shouldUpgrade, checkingRemoteVersion, error } = this.props.upgradeInfo
370
+ if (error) {
371
+ return this.renderError(error)
372
+ }
373
+ if (!shouldUpgrade) {
374
+ return null
375
+ }
376
+ if (checkingRemoteVersion) {
377
+ return null
378
+ }
379
+ return this.renderUpgradePanel()
380
+ }
352
381
  }
@@ -2,7 +2,7 @@
2
2
  position fixed
3
3
  right 10px
4
4
  bottom 10px
5
- z-index 9999
5
+ z-index 888
6
6
  background var(--main)
7
7
  border-radius 5px
8
8
  border 1px solid var(--main-darker)
@@ -22,5 +22,5 @@
22
22
  .upgrade-panel-title
23
23
  border-bottom 1px solid var(--main-darker)
24
24
  .markdown-wrap
25
- max-height 40vh
25
+ max-height 20vh
26
26
  overflow-y auto