@electerm/electerm-react 2.17.16 → 3.0.18

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 (32) hide show
  1. package/client/common/cache.js +4 -2
  2. package/client/common/db.js +3 -5
  3. package/client/common/default-setting.js +1 -1
  4. package/client/common/pass-enc.js +0 -21
  5. package/client/common/safe-local-storage.js +106 -1
  6. package/client/components/ai/ai-chat-history-item.jsx +132 -8
  7. package/client/components/ai/ai-chat.jsx +10 -159
  8. package/client/components/ai/ai.styl +6 -1
  9. package/client/components/bookmark-form/common/render-auth-ssh.jsx +1 -117
  10. package/client/components/bookmark-form/config/common-fields.js +8 -0
  11. package/client/components/bookmark-form/config/ftp.js +1 -0
  12. package/client/components/bookmark-form/config/local.js +10 -51
  13. package/client/components/session/sessions.jsx +1 -0
  14. package/client/components/setting-panel/setting-terminal.jsx +2 -2
  15. package/client/components/sftp/address-bookmark-item.jsx +1 -1
  16. package/client/components/sftp/address-bookmark.jsx +11 -1
  17. package/client/components/sftp/file-item.jsx +1 -1
  18. package/client/components/sftp/sftp-entry.jsx +35 -12
  19. package/client/components/sidebar/history.jsx +1 -1
  20. package/client/components/tabs/app-drag.jsx +13 -12
  21. package/client/components/tabs/tabs.styl +1 -1
  22. package/client/components/terminal/reconnect-overlay.jsx +27 -0
  23. package/client/components/terminal/socket-close-warning.jsx +94 -0
  24. package/client/components/terminal/terminal.jsx +87 -58
  25. package/client/components/terminal/terminal.styl +12 -0
  26. package/client/components/terminal/transfer-client-base.js +3 -3
  27. package/client/components/text-editor/edit-with-custom-editor.jsx +3 -2
  28. package/client/store/init-state.js +3 -3
  29. package/client/store/sync.js +0 -1
  30. package/client/store/tab.js +2 -1
  31. package/client/store/watch.js +3 -3
  32. package/package.json +1 -1
@@ -1,117 +1 @@
1
- /**
2
- * bookmark form auth renderer (copied from legacy)
3
- */
4
- import {
5
- Button,
6
- Input,
7
- AutoComplete,
8
- Form,
9
- Select
10
- } from 'antd'
11
- import { formItemLayout } from '../../../common/form-layout'
12
- import { uniqBy } from 'lodash-es'
13
- import Password from '../../common/password'
14
- import Upload from '../../common/upload'
15
-
16
- const { TextArea } = Input
17
- const FormItem = Form.Item
18
- const e = window.translate
19
-
20
- export default function renderAuth (props) {
21
- const {
22
- store,
23
- form,
24
- authType,
25
- formItemName = 'password',
26
- profileFilter = (d) => d
27
- } = props
28
- const beforeUpload = async (file) => {
29
- const filePath = file.filePath
30
- const privateKey = await window.fs.readFile(filePath)
31
- form.setFieldsValue({
32
- privateKey
33
- })
34
- }
35
- if (authType === 'password') {
36
- const opts = {
37
- options: uniqBy(
38
- store.bookmarks
39
- .filter(d => d.password),
40
- (d) => d.password
41
- )
42
- .map(d => ({
43
- label: `${d.title ? `(${d.title})` : ''}${d.username || ''}:${d.host}-******`,
44
- value: d.password
45
- })),
46
- placeholder: e('password'),
47
- allowClear: false
48
- }
49
- return (
50
- <FormItem
51
- {...formItemLayout}
52
- label={e('password')}
53
- name={formItemName}
54
- hasFeedback
55
- rules={[{
56
- max: 1024, message: '1024 chars max'
57
- }]}
58
- >
59
- <AutoComplete {...opts}>
60
- <Password />
61
- </AutoComplete>
62
- </FormItem>
63
- )
64
- }
65
- if (authType === 'profiles') {
66
- const opts = {
67
- options: store.profiles
68
- .filter(profileFilter)
69
- .map(d => ({ label: d.name, value: d.id })),
70
- placeholder: e('profiles'),
71
- allowClear: true
72
- }
73
- return (
74
- <FormItem
75
- {...formItemLayout}
76
- label={e('profiles')}
77
- name='profile'
78
- hasFeedback
79
- >
80
- <Select {...opts} />
81
- </FormItem>
82
- )
83
- }
84
- return [
85
- <FormItem
86
- {...formItemLayout}
87
- label={e('privateKey')}
88
- hasFeedback
89
- key='privateKey'
90
- className='mg1b'
91
- rules={[{
92
- max: 13000, message: '13000 chars max'
93
- }]}
94
- >
95
- <FormItem noStyle name='privateKey'>
96
- <TextArea placeholder={e('privateKeyDesc')} autoSize={{ minRows: 1 }} />
97
- </FormItem>
98
- <Upload beforeUpload={beforeUpload} fileList={[]}>
99
- <Button type='dashed' className='mg2b mg1t'>
100
- {e('importFromFile')}
101
- </Button>
102
- </Upload>
103
- </FormItem>,
104
- <FormItem
105
- key='passphrase'
106
- {...formItemLayout}
107
- label={e('passphrase')}
108
- name='passphrase'
109
- hasFeedback
110
- rules={[{
111
- max: 1024, message: '1024 chars max'
112
- }]}
113
- >
114
- <Password placeholder={e('passphraseDesc')} />
115
- </FormItem>
116
- ]
117
- }
1
+ export { default } from './ssh-auth-selector'
@@ -239,6 +239,13 @@ export const commonFields = {
239
239
  type: 'runScripts',
240
240
  name: 'runScripts',
241
241
  label: ''
242
+ },
243
+
244
+ enableTerminalImage: {
245
+ type: 'switch',
246
+ name: 'enableTerminalImage',
247
+ label: () => e('enableTerminalImage'),
248
+ valuePropName: 'checked'
242
249
  }
243
250
  }
244
251
 
@@ -272,6 +279,7 @@ export const sshSettings = [
272
279
  label: () => e('ignoreKeyboardInteractive'),
273
280
  valuePropName: 'checked'
274
281
  },
282
+ commonFields.enableTerminalImage,
275
283
  ...terminalSettings.slice(0, -1), // All except terminalBackground
276
284
  commonFields.x11,
277
285
  commonFields.terminalBackground
@@ -34,6 +34,7 @@ const ftpConfig = {
34
34
  { type: 'password', name: 'password', label: () => e('password') },
35
35
  { type: 'switch', name: 'secure', label: () => e('secure'), valuePropName: 'checked' },
36
36
  commonFields.encode,
37
+ commonFields.proxy,
37
38
  commonFields.type
38
39
  ]
39
40
  }
@@ -1,5 +1,5 @@
1
1
  import { formItemLayout } from '../../../common/form-layout.js'
2
- import { terminalLocalType, terminalTypes } from '../../../common/constants.js'
2
+ import { terminalLocalType } from '../../../common/constants.js'
3
3
  import {
4
4
  createBaseInitValues,
5
5
  getTerminalDefaults,
@@ -31,7 +31,8 @@ const localConfig = {
31
31
  commonFields.category,
32
32
  commonFields.colorTitle,
33
33
  commonFields.description,
34
- { type: 'runScripts', name: 'runScripts', label: '' },
34
+ commonFields.enableTerminalImage,
35
+ commonFields.runScripts,
35
36
  { type: 'input', name: 'type', label: 'type', hidden: true }
36
37
  ]
37
38
  },
@@ -39,54 +40,12 @@ const localConfig = {
39
40
  key: 'settings',
40
41
  label: e('settings'),
41
42
  fields: [
42
- {
43
- type: 'input',
44
- name: 'env.LANG',
45
- label: 'ENV:LANG',
46
- props: { maxLength: 130 }
47
- },
48
- {
49
- type: 'autocomplete',
50
- name: 'term',
51
- label: () => e('terminalType'),
52
- rules: [{ required: true, message: 'terminal type required' }],
53
- options: terminalTypes.map(t => ({ label: t, value: t }))
54
- },
55
- {
56
- type: 'switch',
57
- name: 'displayRaw',
58
- label: () => e('displayRaw'),
59
- valuePropName: 'checked'
60
- },
61
- {
62
- type: 'input',
63
- name: 'fontFamily',
64
- label: () => e('fontFamily'),
65
- rules: [{ max: 130, message: '130 chars max' }],
66
- props: { placeholder: defaultSettings.fontFamily }
67
- },
68
- {
69
- type: 'number',
70
- name: 'fontSize',
71
- label: () => e('fontSize'),
72
- props: {
73
- min: 9,
74
- max: 65535,
75
- step: 1,
76
- placeholder: defaultSettings.fontSize
77
- }
78
- },
79
- {
80
- type: 'number',
81
- name: 'keepaliveInterval',
82
- label: () => e('keepaliveIntervalDesc'),
83
- props: {
84
- min: 0,
85
- max: 20000000,
86
- step: 1000
87
- }
88
- },
89
- { type: 'terminalBackground', name: 'terminalBackground', label: () => e('terminalBackgroundImage') },
43
+ commonFields.terminalType,
44
+ commonFields.displayRaw,
45
+ commonFields.fontFamily,
46
+ commonFields.fontSize,
47
+ commonFields.keepaliveInterval,
48
+ commonFields.terminalBackground,
90
49
  // Exec settings - stored as flat properties on bookmark
91
50
  { type: 'execSettings' }
92
51
  ]
@@ -95,7 +54,7 @@ const localConfig = {
95
54
  key: 'quickCommands',
96
55
  label: e('quickCommands'),
97
56
  fields: [
98
- { type: 'quickCommands', name: '__quick__', label: '' }
57
+ commonFields.quickCommands
99
58
  ]
100
59
  }
101
60
  ]
@@ -11,6 +11,7 @@ import pixed from '../layout/pixed'
11
11
  export default class Sessions extends Component {
12
12
  // Function to reload a tab using store.reloadTab
13
13
  reloadTab = (tab) => {
14
+ window.store.updateTab(tab.id, tab)
14
15
  window.store.reloadTab(tab.id)
15
16
  }
16
17
 
@@ -580,7 +580,6 @@ export default class SettingTerminal extends Component {
580
580
  this.renderToggle('saveTerminalLogToFile')
581
581
  }
582
582
  {this.renderToggle('addTimeStampToTermLog')}
583
- {this.renderToggle('enableSixel', 'pd2b', 'SIXEL')}
584
583
  {
585
584
  [
586
585
  'cursorBlink',
@@ -590,7 +589,8 @@ export default class SettingTerminal extends Component {
590
589
  'ctrlOrMetaOpenTerminalLink',
591
590
  'sftpPathFollowSsh',
592
591
  'sshSftpSplitView',
593
- 'showCmdSuggestions'
592
+ 'showCmdSuggestions',
593
+ 'autoReconnectTerminal'
594
594
  ].map(d => this.renderToggle(d))
595
595
  }
596
596
  <div className='pd1b'>{e('terminalBackSpaceMode')}</div>
@@ -68,7 +68,7 @@ export default class AddrBookmarkItem extends Component {
68
68
  onDrop={this.handleDrop}
69
69
  >
70
70
  {globTag}
71
- <b>{item.addr}</b>
71
+ <b className='mg1l'>{item.addr}</b>
72
72
  <CloseCircleOutlined
73
73
  className='del-addr-bookmark'
74
74
  onClick={this.handleDel}
@@ -1,3 +1,4 @@
1
+ import { useState } from 'react'
1
2
  import { auto } from 'manate/react'
2
3
  import {
3
4
  StarOutlined,
@@ -13,6 +14,8 @@ import uid from '../../common/uid'
13
14
  import './address-bookmark.styl'
14
15
 
15
16
  export default auto(function AddrBookmark (props) {
17
+ const [open, setOpen] = useState(false)
18
+
16
19
  function onDel (item) {
17
20
  window.store.delAddressBookmark(item)
18
21
  }
@@ -39,6 +42,11 @@ export default auto(function AddrBookmark (props) {
39
42
  handleAddAddrAct(true)
40
43
  }
41
44
 
45
+ function handleClick (type, addr) {
46
+ setOpen(false)
47
+ onClickHistory(type, addr)
48
+ }
49
+
42
50
  const { type, onClickHistory, host } = props
43
51
  const { store } = window
44
52
  // const cls = classnames(
@@ -58,7 +66,7 @@ export default auto(function AddrBookmark (props) {
58
66
  ? addrs.map(o => {
59
67
  return (
60
68
  <AddrBookmarkItem
61
- handleClick={onClickHistory}
69
+ handleClick={handleClick}
62
70
  type={type}
63
71
  key={o.id}
64
72
  handleDel={onDel}
@@ -100,6 +108,8 @@ export default auto(function AddrBookmark (props) {
100
108
  title={title}
101
109
  placement='bottom'
102
110
  trigger='click'
111
+ open={open}
112
+ onOpenChange={setOpen}
103
113
  >
104
114
  <StarOutlined className={props.className || ''} />
105
115
  </Popover>
@@ -998,7 +998,7 @@ export default class FileSection extends React.Component {
998
998
  const shouldShowSelectedMenu = id &&
999
999
  len > 1 &&
1000
1000
  selectedFiles.has(id)
1001
- const delTxt = shouldShowSelectedMenu ? `${e('deleteAll')}(${len})` : e('del')
1001
+ const delTxt = shouldShowSelectedMenu ? `${e('del')}:${e('selected')}(${len})` : e('del')
1002
1002
  const canPaste = hasFileInClipboardText()
1003
1003
  const showEdit = !isDirectory && id &&
1004
1004
  size < maxEditFileSize
@@ -29,7 +29,7 @@ import fs from '../../common/fs'
29
29
  import ListTable from './list-table-ui'
30
30
  import deepCopy from 'json-deep-copy'
31
31
  import isValidPath from '../../common/is-valid-path'
32
- import { LoadingOutlined } from '@ant-design/icons'
32
+ import { LoadingOutlined, ReloadOutlined } from '@ant-design/icons'
33
33
  import * as owner from './owner-list'
34
34
  import AddressBar from './address-bar'
35
35
  import getProxy from '../../common/get-proxy'
@@ -956,6 +956,20 @@ export default class Sftp extends Component {
956
956
  }, () => this[`${type}List`](undefined, undefined, oldPath))
957
957
  }
958
958
 
959
+ handleReloadRemoteSftp = async () => {
960
+ if (this.sftp) {
961
+ this.sftp.destroy()
962
+ this.sftp = null
963
+ }
964
+ this.setState({
965
+ remoteLoading: true,
966
+ remote: [],
967
+ remoteFileTree: new Map()
968
+ }, () => {
969
+ this.initRemoteAll()
970
+ })
971
+ }
972
+
959
973
  parsePath = (type, pth) => {
960
974
  const reg = /^%([^%]+)%/
961
975
  if (!reg.test(pth)) {
@@ -1152,6 +1166,25 @@ export default class Sftp extends Component {
1152
1166
  )
1153
1167
  }
1154
1168
 
1169
+ renderSftpPanelTitle (type, username, host) {
1170
+ if (type === typeMap.remote) {
1171
+ return (
1172
+ <div className='sftp-panel-title pd1t pd1b pd1x alignright'>
1173
+ <ReloadOutlined
1174
+ className='mg1r pointer'
1175
+ onClick={this.handleReloadRemoteSftp}
1176
+ />
1177
+ {e('remote')}: {username}@{host}
1178
+ </div>
1179
+ )
1180
+ }
1181
+ return (
1182
+ <div className='sftp-panel-title pd1t pd1b pd1x'>
1183
+ {e('local')}
1184
+ </div>
1185
+ )
1186
+ }
1187
+
1155
1188
  renderSection (type, style, width) {
1156
1189
  const {
1157
1190
  id
@@ -1222,17 +1255,7 @@ export default class Sftp extends Component {
1222
1255
  <Spin spinning={loading}>
1223
1256
  <div className='pd1 sftp-panel'>
1224
1257
  {
1225
- type === typeMap.remote
1226
- ? (
1227
- <div className='sftp-panel-title pd1t pd1b pd1x alignright'>
1228
- {e('remote')}: {username}@{host}
1229
- </div>
1230
- )
1231
- : (
1232
- <div className='sftp-panel-title pd1t pd1b pd1x'>
1233
- {e('local')}
1234
- </div>
1235
- )
1258
+ this.renderSftpPanelTitle(type, username, host)
1236
1259
  }
1237
1260
  <AddressBar
1238
1261
  {...addrProps}
@@ -26,7 +26,7 @@ export default auto(function HistoryPanel (props) {
26
26
  } = store
27
27
  let arr = store.config.disableConnectionHistory ? [] : history
28
28
  if (sortByFrequency) {
29
- arr = arr.sort((a, b) => { return b.count - a.count })
29
+ arr = [...arr].sort((a, b) => { return b.count - a.count })
30
30
  }
31
31
 
32
32
  const handleSortByFrequencyChange = (checked) => {
@@ -19,6 +19,19 @@ export default function AppDrag (props) {
19
19
  return true
20
20
  }
21
21
 
22
+ useEffect(() => {
23
+ if (window.store.shouldSendWindowMove) {
24
+ return
25
+ }
26
+ document.addEventListener('mouseup', onMouseUp)
27
+ window.addEventListener('contextmenu', onMouseUp)
28
+
29
+ return () => {
30
+ document.removeEventListener('mouseup', onMouseUp)
31
+ window.removeEventListener('contextmenu', onMouseUp)
32
+ }
33
+ }, [])
34
+
22
35
  function onMouseDown (e) {
23
36
  // e.stopPropagation()
24
37
  if (canOperate(e)) {
@@ -48,18 +61,6 @@ export default function AppDrag (props) {
48
61
  window.pre.runGlobalAsync('maximize')
49
62
  }
50
63
  }
51
- if (!window.store.shouldSendWindowMove) {
52
- useEffect(() => {
53
- // Listen for mouseup at document level to catch mouseup outside window
54
- document.addEventListener('mouseup', onMouseUp)
55
- window.addEventListener('contextmenu', onMouseUp)
56
-
57
- return () => {
58
- document.removeEventListener('mouseup', onMouseUp)
59
- window.removeEventListener('contextmenu', onMouseUp)
60
- }
61
- }, [])
62
- }
63
64
  const props0 = {
64
65
  className: 'app-drag',
65
66
  onDoubleClick
@@ -167,7 +167,7 @@
167
167
  z-index 900
168
168
  border-radius 0 0 3px 3px
169
169
  padding 0
170
- background var(--main-lighter)
170
+ background var(--main)
171
171
 
172
172
  .window-control-box
173
173
  display inline-block
@@ -0,0 +1,27 @@
1
+ import { memo } from 'react'
2
+ import { Button } from 'antd'
3
+ import { LoadingOutlined } from '@ant-design/icons'
4
+
5
+ const e = window.translate
6
+
7
+ export default memo(function ReconnectOverlay ({ countdown, onCancel }) {
8
+ if (countdown === null || countdown === undefined) {
9
+ return null
10
+ }
11
+ return (
12
+ <div className='terminal-reconnect-overlay'>
13
+ <div className='terminal-reconnect-box'>
14
+ <LoadingOutlined className='terminal-reconnect-icon' />
15
+ <div className='terminal-reconnect-msg'>
16
+ {e('autoReconnectTerminal')}: {countdown}s
17
+ </div>
18
+ <Button
19
+ size='small'
20
+ onClick={onCancel}
21
+ >
22
+ {e('cancel')}
23
+ </Button>
24
+ </div>
25
+ </div>
26
+ )
27
+ })
@@ -0,0 +1,94 @@
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { Button } from 'antd'
3
+ import { ReloadOutlined } from '@ant-design/icons'
4
+ import { notification } from '../common/notification'
5
+
6
+ const e = window.translate
7
+ const COUNTDOWN_SECONDS = 3
8
+
9
+ export function showSocketCloseWarning ({
10
+ tabId,
11
+ tab,
12
+ autoReconnect,
13
+ delTab,
14
+ reloadTab
15
+ }) {
16
+ const key = `open${Date.now()}`
17
+
18
+ function closeNotification () {
19
+ notification.destroy(key)
20
+ }
21
+
22
+ const descriptionNode = (
23
+ <SocketCloseDescription
24
+ autoReconnect={autoReconnect}
25
+ onClose={() => {
26
+ closeNotification()
27
+ delTab(tabId)
28
+ }}
29
+ onReload={() => {
30
+ closeNotification()
31
+ reloadTab({ ...tab, autoReConnect: (tab.autoReConnect || 0) + 1 })
32
+ }}
33
+ />
34
+ )
35
+
36
+ notification.warning({
37
+ key,
38
+ message: e('socketCloseTip'),
39
+ duration: autoReconnect ? COUNTDOWN_SECONDS + 2 : 30,
40
+ description: descriptionNode
41
+ })
42
+
43
+ return { key }
44
+ }
45
+
46
+ function SocketCloseDescription ({ autoReconnect, onClose, onReload }) {
47
+ const [countdown, setCountdown] = useState(COUNTDOWN_SECONDS)
48
+ const timerRef = useRef(null)
49
+
50
+ useEffect(() => {
51
+ if (!autoReconnect) {
52
+ return
53
+ }
54
+ timerRef.current = setInterval(() => {
55
+ setCountdown(prev => {
56
+ if (prev <= 1) {
57
+ clearInterval(timerRef.current)
58
+ onReload()
59
+ return 0
60
+ }
61
+ return prev - 1
62
+ })
63
+ }, 1000)
64
+
65
+ return () => {
66
+ if (timerRef.current) {
67
+ clearInterval(timerRef.current)
68
+ }
69
+ }
70
+ }, [autoReconnect])
71
+
72
+ return (
73
+ <div className='pd2y'>
74
+ {autoReconnect && (
75
+ <div className='pd1b'>
76
+ {e('autoReconnectTerminal')}: {countdown}s
77
+ </div>
78
+ )}
79
+ <Button
80
+ className='mg1r'
81
+ type='primary'
82
+ onClick={onClose}
83
+ >
84
+ {e('close')}
85
+ </Button>
86
+ <Button
87
+ icon={<ReloadOutlined />}
88
+ onClick={onReload}
89
+ >
90
+ {e('reload')}
91
+ </Button>
92
+ </div>
93
+ )
94
+ }