@electerm/electerm-react 2.8.16 → 2.10.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.
Files changed (33) hide show
  1. package/client/common/constants.js +3 -3
  2. package/client/common/pre.js +1 -120
  3. package/client/components/bookmark-form/ai-bookmark-form.jsx +324 -0
  4. package/client/components/bookmark-form/bookmark-form.styl +1 -1
  5. package/client/components/bookmark-form/bookmark-schema.js +179 -0
  6. package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
  7. package/client/components/bookmark-form/common/category-select.jsx +2 -4
  8. package/client/components/bookmark-form/common/fields.jsx +0 -10
  9. package/client/components/bookmark-form/config/rdp.js +0 -1
  10. package/client/components/bookmark-form/config/session-config.js +3 -1
  11. package/client/components/bookmark-form/config/spice.js +44 -0
  12. package/client/components/bookmark-form/config/vnc.js +1 -2
  13. package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
  14. package/client/components/bookmark-form/index.jsx +74 -13
  15. package/client/components/session/session.jsx +13 -3
  16. package/client/components/setting-panel/keywords-transport.jsx +0 -1
  17. package/client/components/shortcuts/shortcut-handler.js +11 -5
  18. package/client/components/sidebar/index.jsx +11 -1
  19. package/client/components/spice/spice-session.jsx +276 -0
  20. package/client/components/tabs/add-btn-menu.jsx +9 -2
  21. package/client/components/terminal/attach-addon-custom.js +20 -76
  22. package/client/components/terminal/terminal.jsx +34 -28
  23. package/client/components/terminal/transfer-client-base.js +232 -0
  24. package/client/components/terminal/trzsz-client.js +306 -0
  25. package/client/components/terminal/xterm-loader.js +109 -0
  26. package/client/components/terminal/zmodem-client.js +13 -166
  27. package/client/components/text-editor/simple-editor.jsx +1 -2
  28. package/client/entry/electerm.jsx +0 -2
  29. package/client/store/system-menu.js +10 -0
  30. package/package.json +1 -1
  31. package/client/common/trzsz.js +0 -46
  32. package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
  33. package/client/components/terminal/fs.js +0 -59
@@ -14,7 +14,7 @@ const e = window.translate
14
14
  export default function BookmarkCategorySelect ({
15
15
  bookmarkGroups = [],
16
16
  form,
17
- formItemLayout,
17
+ formItemLayout = defaultFormItemLayout,
18
18
  name = 'category',
19
19
  onChange,
20
20
  formData = {} // Add formData prop to access bookmark ID
@@ -67,11 +67,9 @@ export default function BookmarkCategorySelect ({
67
67
  }
68
68
  }
69
69
 
70
- const layout = formItemLayout || defaultFormItemLayout
71
-
72
70
  return (
73
71
  <FormItem
74
- {...layout}
72
+ {...formItemLayout}
75
73
  label={e('bookmarkCategory')}
76
74
  name={name}
77
75
  >
@@ -21,12 +21,6 @@ import SshHostSelector from './ssh-host-selector.jsx'
21
21
  import SshAuthTypeSelector from './ssh-auth-type-selector.jsx'
22
22
  import SshAuthSelector from './ssh-auth-selector.jsx'
23
23
  import CategorySelect from './category-select.jsx'
24
- import WikiAlert from './wiki-alert.jsx'
25
- import {
26
- rdpWikiLink,
27
- vncWikiLink
28
- } from '../../../common/constants.js'
29
-
30
24
  const Fragment = React.Fragment
31
25
  const FormItem = Form.Item
32
26
 
@@ -126,10 +120,6 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
126
120
  return <Alert key={name} type='info' {...item.props} />
127
121
  case 'warning':
128
122
  return <Alert key={name} type='warning' {...item.props} />
129
- case 'rdpWarning':
130
- return <WikiAlert key={name} wikiUrl={rdpWikiLink} />
131
- case 'vncWarning':
132
- return <WikiAlert key={name} wikiUrl={vncWikiLink} />
133
123
  case 'categorySelect':
134
124
  return (
135
125
  <CategorySelect
@@ -21,7 +21,6 @@ const rdpConfig = {
21
21
  key: 'auth',
22
22
  label: e('auth'),
23
23
  fields: [
24
- { type: 'rdpWarning', name: 'rdpWarning' },
25
24
  commonFields.category,
26
25
  commonFields.colorTitle,
27
26
  { type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
@@ -8,6 +8,7 @@ import serial from './serial'
8
8
  import local from './local'
9
9
  import rdp from './rdp'
10
10
  import ftp from './ftp'
11
+ import spice from './spice'
11
12
 
12
13
  const sessionConfig = {
13
14
  [connectionMap.ssh]: ssh,
@@ -17,7 +18,8 @@ const sessionConfig = {
17
18
  [connectionMap.vnc]: vnc,
18
19
  [connectionMap.rdp]: rdp,
19
20
  [connectionMap.ftp]: ftp,
20
- [connectionMap.web]: web
21
+ [connectionMap.web]: web,
22
+ [connectionMap.spice]: spice
21
23
  }
22
24
 
23
25
  export default sessionConfig
@@ -0,0 +1,44 @@
1
+ import { formItemLayout } from '../../../common/form-layout.js'
2
+ import { terminalSpiceType } from '../../../common/constants.js'
3
+ import { createBaseInitValues, getAuthTypeDefault } from '../common/init-values.js'
4
+ import { isEmpty } from 'lodash-es'
5
+ import { commonFields, connectionHoppingTab } from './common-fields.js'
6
+
7
+ const e = window.translate
8
+
9
+ const spiceConfig = {
10
+ key: 'spice',
11
+ type: terminalSpiceType,
12
+ initValues: (props) => {
13
+ return createBaseInitValues(props, terminalSpiceType, {
14
+ port: 5900,
15
+ viewOnly: false,
16
+ scaleViewport: true,
17
+ connectionHoppings: [],
18
+ ...getAuthTypeDefault(props)
19
+ })
20
+ },
21
+ layout: formItemLayout,
22
+ tabs: () => [
23
+ {
24
+ key: 'auth',
25
+ label: e('auth'),
26
+ fields: [
27
+ commonFields.category,
28
+ commonFields.colorTitle,
29
+ { type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
30
+ commonFields.port,
31
+ { type: 'switch', name: 'viewOnly', label: () => e('viewOnly'), valuePropName: 'checked' },
32
+ { type: 'switch', name: 'scaleViewport', label: () => e('scaleViewport'), valuePropName: 'checked' },
33
+ { type: 'profileItem', name: '__profile__', label: '', profileFilter: d => !isEmpty(d.spice) },
34
+ commonFields.password,
35
+ commonFields.description,
36
+ commonFields.proxy,
37
+ commonFields.type
38
+ ]
39
+ },
40
+ connectionHoppingTab()
41
+ ]
42
+ }
43
+
44
+ export default spiceConfig
@@ -13,7 +13,7 @@ const vncConfig = {
13
13
  return createBaseInitValues(props, terminalVncType, {
14
14
  port: 5900,
15
15
  viewOnly: false,
16
- clipViewport: true,
16
+ clipViewport: false,
17
17
  scaleViewport: true,
18
18
  qualityLevel: 3, // 0-9, lower = faster performance, default 6
19
19
  compressionLevel: 1, // 0-9, lower = faster performance, default 2
@@ -28,7 +28,6 @@ const vncConfig = {
28
28
  key: 'auth',
29
29
  label: e('auth'),
30
30
  fields: [
31
- { type: 'vncWarning', name: 'vncWarning' },
32
31
  commonFields.category,
33
32
  commonFields.colorTitle,
34
33
  { type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
@@ -0,0 +1,134 @@
1
+ import bookmarkSchema from './bookmark-schema.js'
2
+
3
+ const defaultValues = {
4
+ ssh: {
5
+ port: 22,
6
+ enableSsh: true,
7
+ enableSftp: true,
8
+ useSshAgent: true,
9
+ x11: false,
10
+ term: 'xterm-256color',
11
+ displayRaw: false,
12
+ encode: 'utf8',
13
+ envLang: 'en_US.UTF-8'
14
+ },
15
+ telnet: {
16
+ port: 23
17
+ },
18
+ serial: {
19
+ baudRate: 9600,
20
+ dataBits: 8,
21
+ stopBits: 1,
22
+ parity: 'none',
23
+ rtscts: false,
24
+ xon: false,
25
+ xoff: false,
26
+ xany: false
27
+ },
28
+ vnc: {
29
+ port: 5900,
30
+ viewOnly: false,
31
+ clipViewport: false,
32
+ scaleViewport: true,
33
+ qualityLevel: 3,
34
+ compressionLevel: 1,
35
+ shared: true
36
+ },
37
+ rdp: {
38
+ port: 3389
39
+ },
40
+ ftp: {
41
+ port: 21,
42
+ secure: false
43
+ }
44
+ }
45
+
46
+ const requiredFields = {
47
+ ssh: ['host', 'username', 'term'],
48
+ telnet: ['host'],
49
+ serial: ['path'],
50
+ vnc: ['host'],
51
+ rdp: ['host'],
52
+ ftp: ['host'],
53
+ web: ['url']
54
+ }
55
+
56
+ export function fixBookmarkData (data) {
57
+ if (!data || typeof data !== 'object') {
58
+ return data
59
+ }
60
+
61
+ const type = data.type || 'ssh'
62
+ const schema = bookmarkSchema[type]
63
+
64
+ if (!schema) {
65
+ return data
66
+ }
67
+
68
+ const fixed = { ...data }
69
+
70
+ if (!fixed.type) {
71
+ fixed.type = type
72
+ }
73
+
74
+ const defaults = defaultValues[type] || {}
75
+ for (const [key, value] of Object.entries(defaults)) {
76
+ if (fixed[key] === undefined || fixed[key] === null) {
77
+ fixed[key] = value
78
+ }
79
+ }
80
+
81
+ if (fixed.connectionHoppings?.length) {
82
+ fixed.hasHopping = true
83
+ }
84
+
85
+ return fixed
86
+ }
87
+
88
+ export function validateBookmarkData (data) {
89
+ if (!data || typeof data !== 'object') {
90
+ return {
91
+ valid: false,
92
+ errors: ['Invalid data format']
93
+ }
94
+ }
95
+
96
+ const type = data.type || 'ssh'
97
+ const required = requiredFields[type] || []
98
+ const errors = []
99
+
100
+ for (const field of required) {
101
+ if (!data[field]) {
102
+ errors.push(`Missing required field: ${field}`)
103
+ }
104
+ }
105
+
106
+ return {
107
+ valid: errors.length === 0,
108
+ errors
109
+ }
110
+ }
111
+
112
+ export function getMissingRequiredFields (data) {
113
+ if (!data || typeof data !== 'object') {
114
+ return []
115
+ }
116
+
117
+ const type = data.type || 'ssh'
118
+ const required = requiredFields[type] || []
119
+ const missing = []
120
+
121
+ for (const field of required) {
122
+ if (!data[field]) {
123
+ missing.push(field)
124
+ }
125
+ }
126
+
127
+ return missing
128
+ }
129
+
130
+ export default {
131
+ fixBookmarkData,
132
+ validateBookmarkData,
133
+ getMissingRequiredFields
134
+ }
@@ -2,7 +2,7 @@
2
2
  * Config-driven bookmark form (drop-in replacement)
3
3
  */
4
4
  import { PureComponent } from 'react'
5
- import { Radio } from 'antd'
5
+ import { Radio, Button } from 'antd'
6
6
  import {
7
7
  settingMap,
8
8
  connectionMap,
@@ -13,12 +13,14 @@ import {
13
13
  terminalLocalType,
14
14
  terminalTelnetType,
15
15
  terminalFtpType,
16
- newBookmarkIdPrefix
16
+ newBookmarkIdPrefix,
17
+ terminalSpiceType
17
18
  } from '../../common/constants'
18
19
  import { createTitleWithTag } from '../../common/create-title'
19
- import { LoadingOutlined, BookOutlined } from '@ant-design/icons'
20
+ import { LoadingOutlined, BookOutlined, RobotOutlined } from '@ant-design/icons'
20
21
  import sessionConfig from './config/session-config'
21
22
  import renderForm from './render-form'
23
+ import AIBookmarkForm from './ai-bookmark-form'
22
24
  import './bookmark-form.styl'
23
25
 
24
26
  const e = window.translate
@@ -34,27 +36,56 @@ export default class BookmarkIndex2 extends PureComponent {
34
36
  terminalSerialType,
35
37
  terminalRdpType,
36
38
  terminalVncType,
37
- terminalFtpType
39
+ terminalFtpType,
40
+ terminalSpiceType
38
41
  ].includes(initType)) {
39
42
  initType = connectionMap.ssh
40
43
  }
41
- this.state = { ready: false, bookmarkType: initType }
44
+ const v = this.getInitAiModeState()
45
+ this.state = {
46
+ ready: v,
47
+ bookmarkType: initType,
48
+ aiMode: v
49
+ }
42
50
  }
43
51
 
44
52
  componentDidMount () {
45
- this.timer = setTimeout(() => this.setState({ ready: true }), 75)
53
+ this.timer = setTimeout(() => {
54
+ this.setState({ ready: true })
55
+ }, 75)
46
56
  }
47
57
 
48
58
  componentWillUnmount () {
49
59
  clearTimeout(this.timer)
50
60
  }
51
61
 
62
+ getInitAiModeState () {
63
+ const v = window.et.openBookmarkWithAIMode
64
+ if (v !== true) {
65
+ return false
66
+ }
67
+ delete window.et.openBookmarkWithAIMode
68
+ return true
69
+ }
70
+
52
71
  handleChange = (e) => {
53
72
  this.setState({ bookmarkType: e.target.value })
54
73
  }
55
74
 
75
+ handleCancelAiMode = () => {
76
+ this.setState({ aiMode: false })
77
+ }
78
+
79
+ handleToggleAIMode = () => {
80
+ if (window.store.aiConfigMissing()) {
81
+ window.store.toggleAIConfig()
82
+ return
83
+ }
84
+ this.setState(prev => ({ aiMode: !prev.aiMode }))
85
+ }
86
+
56
87
  renderTypes (bookmarkType, isNew, keys) {
57
- if (!isNew) return null
88
+ if (!isNew || this.state.aiMode) return null
58
89
  return (
59
90
  <Radio.Group
60
91
  buttonStyle='solid'
@@ -66,11 +97,7 @@ export default class BookmarkIndex2 extends PureComponent {
66
97
  >
67
98
  {keys.map(v => {
68
99
  const txt = v === 'ssh' ? 'Ssh/Sftp' : e(v)
69
- let sup = null
70
- if (v === connectionMap.vnc || v === connectionMap.rdp) {
71
- sup = <sup className='color-red'>Beta</sup>
72
- }
73
- return (<Radio.Button key={v} value={v}>{txt}{sup}</Radio.Button>)
100
+ return (<Radio.Button key={v} value={v}>{txt}</Radio.Button>)
74
101
  })}
75
102
  </Radio.Group>
76
103
  )
@@ -85,6 +112,39 @@ export default class BookmarkIndex2 extends PureComponent {
85
112
  )
86
113
  }
87
114
 
115
+ renderAIButton (isNew) {
116
+ if (!isNew || this.state.aiMode) {
117
+ return null
118
+ }
119
+ return (
120
+ <Button
121
+ type='primary'
122
+ size='small'
123
+ className='mg2l create-ai-btn'
124
+ icon={<RobotOutlined />}
125
+ onClick={this.handleToggleAIMode}
126
+ >
127
+ {e('createBookmarkByAI')}
128
+ </Button>
129
+ )
130
+ }
131
+
132
+ renderAiForm () {
133
+ return (
134
+ <AIBookmarkForm
135
+ onCancel={this.handleCancelAiMode}
136
+ />
137
+ )
138
+ }
139
+
140
+ renderForm () {
141
+ const { bookmarkType, aiMode } = this.state
142
+ if (aiMode) {
143
+ return this.renderAiForm()
144
+ }
145
+ return renderForm(bookmarkType, this.props)
146
+ }
147
+
88
148
  render () {
89
149
  const { formData } = this.props
90
150
  const { id = '' } = formData
@@ -109,8 +169,9 @@ export default class BookmarkIndex2 extends PureComponent {
109
169
  </span>
110
170
  {this.renderTitle(formData, isNew)}
111
171
  {this.renderTypes(bookmarkType, isNew, keys)}
172
+ {this.renderAIButton(isNew)}
112
173
  </div>
113
- {renderForm(bookmarkType, this.props)}
174
+ {this.renderForm()}
114
175
  </div>
115
176
  )
116
177
  }
@@ -8,6 +8,7 @@ import Sftp from '../sftp/sftp-entry'
8
8
  import RdpSession from '../rdp/rdp-session'
9
9
  import VncSession from '../vnc/vnc-session'
10
10
  import WebSession from '../web/web-session.jsx'
11
+ import SpiceSession from '../spice/spice-session'
11
12
  import {
12
13
  SearchOutlined,
13
14
  FullscreenOutlined,
@@ -29,7 +30,8 @@ import {
29
30
  terminalVncType,
30
31
  terminalWebType,
31
32
  terminalTelnetType,
32
- terminalFtpType
33
+ terminalFtpType,
34
+ terminalSpiceType
33
35
  } from '../../common/constants'
34
36
  import { SplitViewIcon } from '../icons/split-view'
35
37
  import { refs } from '../common/ref'
@@ -269,7 +271,7 @@ export default class SessionWrapper extends Component {
269
271
  />
270
272
  )
271
273
  }
272
- if (type === terminalRdpType || type === terminalVncType) {
274
+ if (type === terminalRdpType || type === terminalVncType || type === terminalSpiceType) {
273
275
  const rdpProps = {
274
276
  tab: this.props.tab,
275
277
  ...pick(this.props, [
@@ -299,6 +301,13 @@ export default class SessionWrapper extends Component {
299
301
  />
300
302
  )
301
303
  }
304
+ if (type === terminalSpiceType) {
305
+ return (
306
+ <SpiceSession
307
+ {...rdpProps}
308
+ />
309
+ )
310
+ }
302
311
 
303
312
  return (
304
313
  <RdpSession
@@ -373,7 +382,8 @@ export default class SessionWrapper extends Component {
373
382
  type === terminalVncType ||
374
383
  type === terminalWebType ||
375
384
  type === terminalTelnetType ||
376
- type === terminalFtpType
385
+ type === terminalFtpType ||
386
+ type === terminalSpiceType
377
387
  }
378
388
 
379
389
  calcSftpWidthHeight = () => {
@@ -27,7 +27,6 @@ export default class KeywordsTransport extends BookmarkTransport {
27
27
  const { store } = this.props
28
28
  const arr = store.config.keywords || []
29
29
  const txt = JSON.stringify(arr, null, 2)
30
- console.log(txt, 'txt')
31
30
  const stamp = time(undefined, 'YYYY-MM-DD-HH-mm-ss')
32
31
  download('electerm-' + this.name + '-' + stamp + '.json', txt)
33
32
  }
@@ -175,12 +175,18 @@ export function shortcutExtend (Cls) {
175
175
  type === 'keydown' &&
176
176
  !altKey &&
177
177
  !shiftKey &&
178
- ctrlKey &&
179
- this.zmodemClient &&
180
- this.zmodemClient.isActive
178
+ ctrlKey
181
179
  ) {
182
- this.zmodemClient.cancel()
183
- return false
180
+ // Cancel zmodem transfer if active
181
+ if (this.zmodemClient && this.zmodemClient.isActive) {
182
+ this.zmodemClient.cancel()
183
+ return false
184
+ }
185
+ // Cancel trzsz transfer if active
186
+ if (this.trzszClient && this.trzszClient.isActive) {
187
+ this.trzszClient.cancel()
188
+ return false
189
+ }
184
190
  }
185
191
 
186
192
  let codeName
@@ -7,7 +7,8 @@ import {
7
7
  SettingOutlined,
8
8
  UpCircleOutlined,
9
9
  BarsOutlined,
10
- AppstoreOutlined
10
+ AppstoreOutlined,
11
+ RobotOutlined
11
12
  } from '@ant-design/icons'
12
13
  import { Tooltip } from 'antd'
13
14
  import SideBarPanel from './sidebar-panel'
@@ -86,6 +87,7 @@ export default function Sidebar (props) {
86
87
 
87
88
  const {
88
89
  onNewSsh,
90
+ onNewSshAI,
89
91
  openSetting,
90
92
  openAbout,
91
93
  openSettingSync,
@@ -142,6 +144,14 @@ export default function Sidebar (props) {
142
144
  onClick={onNewSsh}
143
145
  />
144
146
  </SideIcon>
147
+ <SideIcon
148
+ title={e('createBookmarkByAI')}
149
+ >
150
+ <RobotOutlined
151
+ className='font20 iblock control-icon'
152
+ onClick={onNewSshAI}
153
+ />
154
+ </SideIcon>
145
155
  <SideIcon
146
156
  title={e(settingMap.bookmarks)}
147
157
  active={bookmarksActive}