@electerm/electerm-react 1.39.2 → 1.39.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 (27) hide show
  1. package/client/common/constants.js +2 -0
  2. package/client/components/bookmark-form/form-ssh-common.jsx +1 -1
  3. package/client/components/bookmark-form/rdp-form-ui.jsx +37 -2
  4. package/client/components/bookmark-form/render-ssh-tunnel.jsx +60 -31
  5. package/client/components/bookmark-form/use-ui.jsx +12 -0
  6. package/client/components/bookmark-form/web-form-ui.jsx +31 -1
  7. package/client/components/footer/footer-entry.jsx +12 -6
  8. package/client/components/main/main.jsx +1 -1
  9. package/client/components/quick-commands/quick-commands-list-form.jsx +54 -3
  10. package/client/components/{session → rdp}/rdp-session.jsx +7 -2
  11. package/client/components/session/session.jsx +1 -1
  12. package/client/components/session/sessions.jsx +1 -1
  13. package/client/components/setting-panel/setting-modal.jsx +9 -6
  14. package/client/components/setting-panel/setting-wrap.jsx +5 -1
  15. package/client/components/setting-panel/setting-wrap.styl +5 -3
  16. package/client/components/tabs/index.jsx +1 -1
  17. package/client/components/tabs/tab.jsx +9 -4
  18. package/client/components/terminal/index.jsx +11 -2
  19. package/client/components/terminal/term-search.jsx +12 -0
  20. package/client/store/load-data.js +9 -1
  21. package/client/store/sync.js +7 -3
  22. package/package.json +1 -1
  23. /package/client/components/{session → rdp}/code-scan.js +0 -0
  24. /package/client/components/{session → rdp}/resolution-edit.jsx +0 -0
  25. /package/client/components/{session → rdp}/resolution-form.jsx +0 -0
  26. /package/client/components/{session → rdp}/resolutions.js +0 -0
  27. /package/client/components/{session → web}/web-session.jsx +0 -0
@@ -238,6 +238,7 @@ export const terminalActions = {
238
238
  openTerminalSearch: 'open-terminal-search',
239
239
  doSearchNext: 'do-search-next',
240
240
  doSearchPrev: 'do-search-prev',
241
+ clearSearch: 'clear-search',
241
242
  zoom: 'zoom-terminal'
242
243
  }
243
244
  export const fileActions = {
@@ -317,6 +318,7 @@ export const sshTunnelHelpLink = 'https://github.com/electerm/electerm/wiki/How-
317
318
  export const batchOpHelpLink = 'https://github.com/electerm/electerm/wiki/batch-operation'
318
319
  export const proxyHelpLink = 'https://github.com/electerm/electerm/wiki/proxy-format'
319
320
  export const regexHelpLink = 'https://github.com/electerm/electerm/wiki/Terminal-keywords-highlight-regular-expression-exmaples'
321
+ export const rdpHelpLink = 'https://github.com/electerm/electerm/wiki/RDP-limitation'
320
322
  export const modals = {
321
323
  hide: 0,
322
324
  setting: 1,
@@ -209,7 +209,7 @@ export default function renderCommon (props) {
209
209
  value={k}
210
210
  key={k}
211
211
  >
212
- {k}
212
+ {k.toUpperCase()}
213
213
  </Option>
214
214
  )
215
215
  })
@@ -6,22 +6,28 @@ import { useEffect } from 'react'
6
6
  import {
7
7
  Input,
8
8
  Form,
9
- InputNumber
9
+ InputNumber,
10
+ TreeSelect
10
11
  } from 'antd'
11
12
  import { formItemLayout } from '../../common/form-layout'
12
13
  import {
13
14
  newBookmarkIdPrefix,
14
- terminalRdpType
15
+ terminalRdpType,
16
+ rdpHelpLink
15
17
  } from '../../common/constants'
16
18
  import useSubmit from './use-submit'
17
19
  import copy from 'json-deep-copy'
20
+ import Link from '../common/external-link.jsx'
18
21
  import { defaults } from 'lodash-es'
19
22
  import { ColorPickerItem } from './color-picker-item.jsx'
20
23
  import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
24
+ import formatBookmarkGroups from './bookmark-group-tree-format'
25
+ import findBookmarkGroupId from '../../common/find-bookmark-group-id'
21
26
 
22
27
  const FormItem = Form.Item
23
28
  const { prefix } = window
24
29
  const e = prefix('form')
30
+ const c = prefix('common')
25
31
 
26
32
  export default function LocalFormUi (props) {
27
33
  const [
@@ -36,16 +42,34 @@ export default function LocalFormUi (props) {
36
42
  })
37
43
  }
38
44
  }, [props.currentBookmarkGroupId])
45
+ const {
46
+ id = ''
47
+ } = props.formData
48
+ const {
49
+ bookmarkGroups = [],
50
+ currentBookmarkGroupId
51
+ } = props
39
52
  let initialValues = copy(props.formData)
53
+ const initBookmarkGroupId = !id.startsWith(newBookmarkIdPrefix)
54
+ ? findBookmarkGroupId(bookmarkGroups, id)
55
+ : currentBookmarkGroupId
40
56
  const defaultValues = {
41
57
  type: terminalRdpType,
42
58
  port: 3389,
59
+ category: initBookmarkGroupId,
43
60
  color: getRandomDefaultColor()
44
61
  }
45
62
  initialValues = defaults(initialValues, defaultValues)
46
63
  function renderCommon () {
64
+ const {
65
+ bookmarkGroups = []
66
+ } = props
67
+ const tree = formatBookmarkGroups(bookmarkGroups)
47
68
  return (
48
69
  <div className='pd1x'>
70
+ <p className='alignright'>
71
+ <Link to={rdpHelpLink}>Wiki: {rdpHelpLink}</Link>
72
+ </p>
49
73
  <FormItem
50
74
  {...formItemLayout}
51
75
  label={e('title')}
@@ -114,6 +138,17 @@ export default function LocalFormUi (props) {
114
138
  >
115
139
  <Input />
116
140
  </FormItem>
141
+ <FormItem
142
+ {...formItemLayout}
143
+ label={c('bookmarkCategory')}
144
+ name='category'
145
+ >
146
+ <TreeSelect
147
+ treeData={tree}
148
+ treeDefaultExpandAll
149
+ showSearch
150
+ />
151
+ </FormItem>
117
152
  <FormItem
118
153
  {...formItemLayout}
119
154
  label='type'
@@ -35,10 +35,14 @@ export default function renderSshTunnels (props) {
35
35
  sshTunnelRemotePort: 12300,
36
36
  sshTunnelRemoteHost: '127.0.0.1'
37
37
  })
38
+ const [isDynamic, setter] = useState(formData.sshTunnel === 'dynamicForward')
38
39
  const [list, setList] = useState(formData.sshTunnels || [])
39
40
  function onSubmit () {
40
41
  formChild.submit()
41
42
  }
43
+ function onChange (e) {
44
+ setter(e.target.value === 'dynamicForward')
45
+ }
42
46
  function handleFinish (data) {
43
47
  const nd = {
44
48
  ...data,
@@ -80,7 +84,7 @@ export default function renderSshTunnels (props) {
80
84
  title: e('sshTunnel'),
81
85
  key: 'sshTunnel',
82
86
  render: (k, item) => {
83
- // sshTunnel is forwardRemoteToLocal or forwardLocalToRemote
87
+ // sshTunnel is forwardRemoteToLocal or forwardLocalToRemote or dynamicForward
84
88
  const {
85
89
  sshTunnel,
86
90
  sshTunnelRemoteHost = '127.0.0.1',
@@ -89,6 +93,9 @@ export default function renderSshTunnels (props) {
89
93
  sshTunnelLocalPort,
90
94
  name
91
95
  } = item
96
+ if (sshTunnel === 'dynamicForward') {
97
+ return `socks5://${sshTunnelLocalHost}:${sshTunnelLocalPort}`
98
+ }
92
99
  const to = sshTunnel === 'forwardRemoteToLocal'
93
100
  ? `${s('local')}:${sshTunnelLocalHost}:${sshTunnelLocalPort}`
94
101
  : `${s('remote')}:${sshTunnelRemoteHost}:${sshTunnelRemotePort}`
@@ -148,6 +155,49 @@ export default function renderSshTunnels (props) {
148
155
  )
149
156
  }
150
157
 
158
+ function renderDynamicForward () {
159
+ return (
160
+ <p><UserOutlined /> → socks proxy → url</p>
161
+ )
162
+ }
163
+
164
+ function renderRemote () {
165
+ if (isDynamic) {
166
+ return null
167
+ }
168
+ return (
169
+ <FormItem
170
+ label={s('remote')}
171
+ {...formItemLayout}
172
+ required
173
+ className='ssh-tunnels-host'
174
+ >
175
+ <Space.Compact>
176
+ <FormItem
177
+ name='sshTunnelRemoteHost'
178
+ label=''
179
+ required
180
+ >
181
+ <Input
182
+ placeholder={e('host')}
183
+ />
184
+ </FormItem>
185
+ <FormItem
186
+ label=''
187
+ name='sshTunnelRemotePort'
188
+ required
189
+ >
190
+ <InputNumber
191
+ min={1}
192
+ max={65535}
193
+ placeholder={e('port')}
194
+ />
195
+ </FormItem>
196
+ </Space.Compact>
197
+ </FormItem>
198
+ )
199
+ }
200
+
151
201
  return (
152
202
  <div>
153
203
  <FormItem
@@ -168,7 +218,7 @@ export default function renderSshTunnels (props) {
168
218
  {...formItemLayout}
169
219
  required
170
220
  >
171
- <RadioGroup>
221
+ <RadioGroup onChange={onChange}>
172
222
  <RadioButton
173
223
  value='forwardRemoteToLocal'
174
224
  >
@@ -183,37 +233,16 @@ export default function renderSshTunnels (props) {
183
233
  <span>L→R <QuestionCircleOutlined /></span>
184
234
  </Tooltip>
185
235
  </RadioButton>
186
- </RadioGroup>
187
- </FormItem>
188
- <FormItem
189
- label={s('remote')}
190
- {...formItemLayout}
191
- required
192
- className='ssh-tunnels-host'
193
- >
194
- <Space.Compact>
195
- <FormItem
196
- name='sshTunnelRemoteHost'
197
- label=''
198
- required
199
- >
200
- <Input
201
- placeholder={e('host')}
202
- />
203
- </FormItem>
204
- <FormItem
205
- label=''
206
- name='sshTunnelRemotePort'
207
- required
236
+ <RadioButton
237
+ value='dynamicForward'
208
238
  >
209
- <InputNumber
210
- min={1}
211
- max={65535}
212
- placeholder={e('port')}
213
- />
214
- </FormItem>
215
- </Space.Compact>
239
+ <Tooltip title={renderDynamicForward()}>
240
+ <span>{e('dynamicForward')}(socks proxy) <QuestionCircleOutlined /></span>
241
+ </Tooltip>
242
+ </RadioButton>
243
+ </RadioGroup>
216
244
  </FormItem>
245
+ {renderRemote()}
217
246
  <FormItem
218
247
  label={s('local')}
219
248
  {...formItemLayout}
@@ -87,6 +87,18 @@ export default function useBookmarkFormUI (props) {
87
87
  step={1}
88
88
  placeholder={defaultFontSize}
89
89
  />
90
+ </FormItem>,
91
+ <FormItem
92
+ key='keepaliveInterval'
93
+ {...formItemLayout}
94
+ label={s('keepaliveIntervalDesc')}
95
+ name='keepaliveInterval'
96
+ >
97
+ <InputNumber
98
+ min={0}
99
+ max={20000000}
100
+ step={1000}
101
+ />
90
102
  </FormItem>
91
103
  ]
92
104
  }
@@ -5,7 +5,8 @@
5
5
  import { useEffect } from 'react'
6
6
  import {
7
7
  Input,
8
- Form
8
+ Form,
9
+ TreeSelect
9
10
  } from 'antd'
10
11
  import { formItemLayout } from '../../common/form-layout'
11
12
  import {
@@ -17,10 +18,13 @@ import copy from 'json-deep-copy'
17
18
  import { defaults } from 'lodash-es'
18
19
  import { ColorPickerItem } from './color-picker-item.jsx'
19
20
  import { getRandomDefaultColor } from '../../common/rand-hex-color.js'
21
+ import formatBookmarkGroups from './bookmark-group-tree-format'
22
+ import findBookmarkGroupId from '../../common/find-bookmark-group-id'
20
23
 
21
24
  const FormItem = Form.Item
22
25
  const { prefix } = window
23
26
  const e = prefix('form')
27
+ const c = prefix('common')
24
28
 
25
29
  export default function LocalFormUi (props) {
26
30
  const [
@@ -35,13 +39,28 @@ export default function LocalFormUi (props) {
35
39
  })
36
40
  }
37
41
  }, [props.currentBookmarkGroupId])
42
+ const {
43
+ id = ''
44
+ } = props.formData
45
+ const {
46
+ bookmarkGroups = [],
47
+ currentBookmarkGroupId
48
+ } = props
49
+ const initBookmarkGroupId = !id.startsWith(newBookmarkIdPrefix)
50
+ ? findBookmarkGroupId(bookmarkGroups, id)
51
+ : currentBookmarkGroupId
38
52
  let initialValues = copy(props.formData)
39
53
  const defaultValues = {
40
54
  type: terminalWebType,
55
+ category: initBookmarkGroupId,
41
56
  color: getRandomDefaultColor()
42
57
  }
43
58
  initialValues = defaults(initialValues, defaultValues)
44
59
  function renderCommon () {
60
+ const {
61
+ bookmarkGroups = []
62
+ } = props
63
+ const tree = formatBookmarkGroups(bookmarkGroups)
45
64
  return (
46
65
  <div className='pd1x'>
47
66
  <FormItem
@@ -70,6 +89,17 @@ export default function LocalFormUi (props) {
70
89
  >
71
90
  <Input.TextArea rows={1} />
72
91
  </FormItem>
92
+ <FormItem
93
+ {...formItemLayout}
94
+ label={c('bookmarkCategory')}
95
+ name='category'
96
+ >
97
+ <TreeSelect
98
+ treeData={tree}
99
+ treeDefaultExpandAll
100
+ showSearch
101
+ />
102
+ </FormItem>
73
103
  <FormItem
74
104
  {...formItemLayout}
75
105
  label='type'
@@ -76,21 +76,27 @@ export default class SystemMenu extends Component {
76
76
  }
77
77
 
78
78
  renderEncodingInfo () {
79
+ const selectProps = {
80
+ style: {
81
+ minWidth: 30
82
+ },
83
+ placeholder: f('encode'),
84
+ defaultValue: this.props.currentTab?.encode,
85
+ onSelect: this.handleSwitchEncoding,
86
+ size: 'small',
87
+ popupMatchSelectWidth: false
88
+ }
79
89
  return (
80
90
  <div className='terminal-footer-unit terminal-footer-info'>
81
91
  <div className='fleft relative'>
82
92
  <Select
83
- style={{ minWidth: 30 }}
84
- placeholder={f('encode')}
85
- defaultValue={this.props.currentTab?.encode}
86
- onSelect={this.handleSwitchEncoding}
87
- size='small'
93
+ {...selectProps}
88
94
  >
89
95
  {
90
96
  encodes.map(k => {
91
97
  return (
92
98
  <Option key={k} value={k}>
93
- {k}
99
+ {k.toUpperCase()}
94
100
  </Option>
95
101
  )
96
102
  })
@@ -11,7 +11,7 @@ import BatchOp from '../batch-op/batch-op'
11
11
  import CssOverwrite from './css-overwrite'
12
12
  import UiTheme from './ui-theme'
13
13
  import CustomCss from './custom-css.jsx'
14
- import Resolutions from '../session/resolution-edit'
14
+ import Resolutions from '../rdp/resolution-edit'
15
15
  import TerminalInteractive from '../terminal/terminal-interactive'
16
16
  import ConfirmModalStore from '../sftp/confirm-modal-store.jsx'
17
17
  import TransferConflictStore from '../sftp/transfer-conflict-store.jsx'
@@ -3,10 +3,14 @@ import {
3
3
  InputNumber,
4
4
  Space,
5
5
  Button,
6
- Input
6
+ Input,
7
+ Tag
7
8
  } from 'antd'
8
9
  import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'
9
10
  import { formItemLayout } from '../../common/form-layout'
11
+ import HelpIcon from '../common/help-icon'
12
+ import { copy } from '../../common/clipboard'
13
+ import { useRef } from 'react'
10
14
 
11
15
  const FormItem = Form.Item
12
16
  const FormList = Form.List
@@ -14,6 +18,7 @@ const { prefix } = window
14
18
  const t = prefix('quickCommands')
15
19
 
16
20
  export default function renderQm () {
21
+ const focused = useRef(0)
17
22
  function renderItem (field, i, add, remove) {
18
23
  return (
19
24
  <Space
@@ -43,7 +48,10 @@ export default function renderQm () {
43
48
  <Input.TextArea
44
49
  rows={1}
45
50
  placeholder={t('quickCommand')}
46
- className='compact-input'
51
+ className='compact-input qm-input'
52
+ onFocus={() => {
53
+ focused.current = i
54
+ }}
47
55
  />
48
56
  </FormItem>
49
57
  <Button
@@ -54,9 +62,52 @@ export default function renderQm () {
54
62
  </Space>
55
63
  )
56
64
  }
65
+ const commonCmds = [
66
+ { cmd: 'ls', desc: 'List directory contents' },
67
+ { cmd: 'cd', desc: 'Change the current directory' },
68
+ { cmd: 'pwd', desc: 'Print the current working directory' },
69
+ { cmd: 'cp', desc: 'Copy files and directories' },
70
+ { cmd: 'mv', desc: 'Move/rename files and directories' },
71
+ { cmd: 'rm', desc: 'Remove files or directories' },
72
+ { cmd: 'mkdir', desc: 'Create new directories' },
73
+ { cmd: 'rmdir', desc: 'Remove empty directories' },
74
+ { cmd: 'touch', desc: 'Create empty files or update file timestamps' },
75
+ { cmd: 'chmod', desc: 'Change file modes or Access Control Lists' },
76
+ { cmd: 'chown', desc: 'Change file owner and group' },
77
+ { cmd: 'cat', desc: 'Concatenate and display file content' },
78
+ { cmd: 'echo', desc: 'Display message or variable value' },
79
+ { cmd: 'grep', desc: 'Search text using patterns' },
80
+ { cmd: 'find', desc: 'Search for files in a directory hierarchy' },
81
+ { cmd: 'df', desc: 'Report file system disk space usage' },
82
+ { cmd: 'du', desc: 'Estimate file space usage' },
83
+ { cmd: 'top', desc: 'Display Linux tasks' },
84
+ { cmd: 'ps', desc: 'Report a snapshot of current processes' },
85
+ { cmd: 'kill', desc: 'Send a signal to a process' }
86
+ ]
57
87
 
88
+ const cmds = commonCmds.map(c => {
89
+ return (
90
+ <Tag
91
+ title={c.desc}
92
+ key={c.cmd}
93
+ onClick={() => {
94
+ copy(c.cmd)
95
+ }}
96
+ >
97
+ <b className='pointer'>{c.cmd}</b>
98
+ </Tag>
99
+ )
100
+ })
101
+ const label = (
102
+ <div>
103
+ {t('commonCommands')}
104
+ <HelpIcon
105
+ title={cmds}
106
+ />
107
+ </div>
108
+ )
58
109
  return (
59
- <FormItem {...formItemLayout} label={t('quickCommands')}>
110
+ <FormItem {...formItemLayout} label={label}>
60
111
  <FormList
61
112
  name='commands'
62
113
  >
@@ -4,7 +4,8 @@ import deepCopy from 'json-deep-copy'
4
4
  import clone from '../../common/to-simple-obj'
5
5
  import { handleErr } from '../../common/fetch'
6
6
  import {
7
- statusMap
7
+ statusMap,
8
+ rdpHelpLink
8
9
  } from '../../common/constants'
9
10
  import {
10
11
  notification,
@@ -16,6 +17,7 @@ import {
16
17
  ReloadOutlined,
17
18
  EditOutlined
18
19
  } from '@ant-design/icons'
20
+ import HelpIcon from '../common/help-icon'
19
21
  import * as ls from '../../common/safe-local-storage'
20
22
  import scanCode from './code-scan'
21
23
  import resolutions from './resolutions'
@@ -388,9 +390,12 @@ export default class RdpSession extends Component {
388
390
  onClick={this.handleEditResolutions}
389
391
  className='mg2r mg1l pointer'
390
392
  />
391
- <span className='mg2l'>
393
+ <span className='mg2l mg2r'>
392
394
  {username}@{host}:{port}
393
395
  </span>
396
+ <HelpIcon
397
+ link={rdpHelpLink}
398
+ />
394
399
  </div>
395
400
  )
396
401
  }
@@ -4,7 +4,7 @@
4
4
  import { Component } from 'react'
5
5
  import Term from '../terminal'
6
6
  import Sftp from '../sftp/sftp-entry'
7
- import RdpSession from './rdp-session'
7
+ import RdpSession from '../rdp/rdp-session'
8
8
  import {
9
9
  BorderVerticleOutlined,
10
10
  BorderHorizontalOutlined,
@@ -1,6 +1,6 @@
1
1
  import { Component } from '../common/react-subx'
2
2
  import Session from './session'
3
- import WebSession from './web-session'
3
+ import WebSession from '../web/web-session.jsx'
4
4
  import { findIndex, pick } from 'lodash-es'
5
5
  import classNames from 'classnames'
6
6
  import generate from '../../common/uid'
@@ -94,15 +94,18 @@ export default class SettingModalWrap extends Component {
94
94
  children: null
95
95
  }
96
96
  ]
97
+ const tabsProps = {
98
+ activeKey: settingTab,
99
+ animated: false,
100
+ items,
101
+ onChange: store.handleChangeSettingTab,
102
+ destroyInactiveTabPane: true,
103
+ className: 'setting-tabs'
104
+ }
97
105
  return (
98
106
  <div>
99
107
  <Tabs
100
- activeKey={settingTab}
101
- animated={false}
102
- items={items}
103
- onChange={store.handleChangeSettingTab}
104
- destroyInactiveTabPane
105
- className='setting-tabs'
108
+ {...tabsProps}
106
109
  />
107
110
  <TabHistory
108
111
  listProps={props0}
@@ -36,7 +36,11 @@ export default class SettingWrap extends Component {
36
36
  {...pops}
37
37
  >
38
38
  <CloseCircleOutlined
39
- className='close-setting-wrap'
39
+ className='close-setting-wrap-icon close-setting-wrap'
40
+ onClick={this.props.onCancel}
41
+ />
42
+ <CloseCircleOutlined
43
+ className='close-setting-wrap-icon alt-close-setting-wrap'
40
44
  onClick={this.props.onCancel}
41
45
  />
42
46
  {
@@ -5,10 +5,8 @@
5
5
 
6
6
  body .ant-drawer .ant-drawer-content-wrapper
7
7
  left 43px
8
-
9
- .close-setting-wrap
8
+ .close-setting-wrap-icon
10
9
  position absolute
11
- left 20px
12
10
  top 70px
13
11
  font-size 16px
14
12
  color text
@@ -16,6 +14,10 @@ body .ant-drawer .ant-drawer-content-wrapper
16
14
  z-index 889
17
15
  &:hover
18
16
  color text
17
+ .alt-close-setting-wrap
18
+ right 20px
19
+ .close-setting-wrap
20
+ top 70px
19
21
  .setting-row
20
22
  position absolute
21
23
  top 127px
@@ -258,7 +258,7 @@ export default class Tabs extends React.Component {
258
258
  : tabsWidthAll
259
259
  const w1 = isMacJs ? 30 : windowControlWidth
260
260
  const style = {
261
- width: width - w1 - 136
261
+ width: width - w1 - 166
262
262
  }
263
263
  return (
264
264
  <div
@@ -358,9 +358,14 @@ class Tab extends Component {
358
358
  sshTunnelLocalHost = '127.0.0.1',
359
359
  name
360
360
  } = obj
361
- let tunnel = sshTunnel === 'forwardRemoteToLocal'
362
- ? `-> ${s('remote')}:${sshTunnelRemoteHost}:${sshTunnelRemotePort} -> ${sshTunnelLocalHost}:${sshTunnelLocalPort}`
363
- : `-> ${s('local')}:${sshTunnelLocalHost}:${sshTunnelLocalPort} -> ${sshTunnelRemoteHost}:${sshTunnelRemotePort}`
361
+ let tunnel
362
+ if (sshTunnel === 'dynamicForward') {
363
+ tunnel = `sock5://${sshTunnelLocalHost}:${sshTunnelLocalPort}`
364
+ } else {
365
+ tunnel = sshTunnel === 'forwardRemoteToLocal'
366
+ ? `-> ${s('remote')}:${sshTunnelRemoteHost}:${sshTunnelRemotePort} -> ${sshTunnelLocalHost}:${sshTunnelLocalPort}`
367
+ : `-> ${s('local')}:${sshTunnelLocalHost}:${sshTunnelLocalPort} -> ${sshTunnelRemoteHost}:${sshTunnelRemotePort}`
368
+ }
364
369
  if (error) {
365
370
  tunnel = `error: ${tunnel}`
366
371
  }
@@ -371,7 +376,7 @@ class Tab extends Component {
371
376
  })
372
377
  return (
373
378
  <div>
374
- <div>${title}</div>
379
+ <div>{title}</div>
375
380
  {list}
376
381
  </div>
377
382
  )
@@ -363,6 +363,13 @@ class Term extends Component {
363
363
  )
364
364
  ) {
365
365
  this.searchPrev(keyword, options)
366
+ } else if (
367
+ action === terminalActions.clearSearch &&
368
+ (
369
+ propSplitId === activeSplitId
370
+ )
371
+ ) {
372
+ this.searchAddon.clearDecorations()
366
373
  } else if (
367
374
  action === commonActions.getTermLogState &&
368
375
  pid === statePid
@@ -1018,8 +1025,9 @@ class Term extends Component {
1018
1025
  const cmd = `ssh ${title.split(/\s/g)[0]}\r`
1019
1026
  return this.attachAddon._sendData(cmd)
1020
1027
  }
1021
- if (startDirectory) {
1022
- const cmd = `cd ${startDirectory}\r`
1028
+ const startFolder = startDirectory || window.initFolder
1029
+ if (startFolder) {
1030
+ const cmd = `cd ${startFolder}\r`
1023
1031
  this.attachAddon._sendData(cmd)
1024
1032
  }
1025
1033
  if (runScripts && runScripts.length) {
@@ -1119,6 +1127,7 @@ class Term extends Component {
1119
1127
  'execLinuxArgs',
1120
1128
  'debug'
1121
1129
  ]),
1130
+ keepaliveInterval: tab.keepaliveInterval === undefined ? config.keepaliveInterval : tab.keepaliveInterval,
1122
1131
  sessionId,
1123
1132
  tabId: id,
1124
1133
  srcTabId: tab.id,
@@ -55,6 +55,9 @@ class TermSearch extends Component {
55
55
  }
56
56
 
57
57
  toggleSearch = () => {
58
+ if (this.props.store.termSearchOpen) {
59
+ this.clearSearch()
60
+ }
58
61
  this.props.store.toggleTerminalSearch()
59
62
  setTimeout(window.store.focus, 200)
60
63
  }
@@ -89,7 +92,16 @@ class TermSearch extends Component {
89
92
  this.prev()
90
93
  }
91
94
 
95
+ clearSearch = () => {
96
+ const { activeTerminalId } = this.props.store
97
+ postMessage({
98
+ action: terminalActions.clearSearch,
99
+ activeSplitId: activeTerminalId
100
+ })
101
+ }
102
+
92
103
  close = () => {
104
+ this.clearSearch()
93
105
  this.props.store.termSearchOpen = false
94
106
  }
95
107
 
@@ -60,7 +60,8 @@ export async function addTabFromCommandLine (store, opts) {
60
60
  enableSsh: !options.sftpOnly,
61
61
  authType: 'password',
62
62
  pane: options.type || 'terminal',
63
- term: defaultSettings.terminalType
63
+ term: defaultSettings.terminalType,
64
+ startDirectoryLocal: options.initFolder
64
65
  }
65
66
  if (options.setEnv) {
66
67
  update.setEnv = options.setEnv
@@ -79,8 +80,15 @@ export async function addTabFromCommandLine (store, opts) {
79
80
  conf.privateKey = await fs.readFile(options.privateKeyPath)
80
81
  }
81
82
  log.debug('command line opts', conf)
83
+ console.log(options.initFolder, !(store.config.onStartSessions || []).length, store.config.initDefaultTabOnStart)
82
84
  if (conf.username && conf.host) {
83
85
  store.addTab(conf)
86
+ } else if (
87
+ options.initFolder &&
88
+ !(store.config.onStartSessions || []).length &&
89
+ store.config.initDefaultTabOnStart
90
+ ) {
91
+ window.initFolder = options.initFolder
84
92
  }
85
93
  if (options && options.batchOp) {
86
94
  window.store.runBatchOp(options.batchOp)
@@ -233,9 +233,13 @@ export default (Store) => {
233
233
  for (const n of names) {
234
234
  let str = get(gist, `files["${n}.json"].content`)
235
235
  if (!str) {
236
- store.isSyncingSetting = false
237
- store.isSyncDownload = false
238
- throw new Error(('Seems you have a empty gist, you can try use existing gist ID or upload first'))
236
+ if (n === settingMap.bookmarks) {
237
+ store.isSyncingSetting = false
238
+ store.isSyncDownload = false
239
+ throw new Error(('Seems you have a empty gist, you can try use existing gist ID or upload first'))
240
+ } else {
241
+ continue
242
+ }
239
243
  }
240
244
  if (!isJSON(str)) {
241
245
  str = await window.pre.runGlobalAsync('decryptAsync', str, pass)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.39.2",
3
+ "version": "1.39.18",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
File without changes