@electerm/electerm-react 1.39.5 → 1.39.31

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 (45) hide show
  1. package/client/common/constants.js +7 -3
  2. package/client/common/create-title.jsx +12 -2
  3. package/client/common/default-setting.js +5 -0
  4. package/client/common/init-setting-item.js +6 -0
  5. package/client/components/bookmark-form/form-ssh-common.jsx +1 -1
  6. package/client/components/bookmark-form/index.jsx +6 -2
  7. package/client/components/bookmark-form/rdp-form-ui.jsx +1 -1
  8. package/client/components/bookmark-form/rdp-form.jsx +1 -1
  9. package/client/components/bookmark-form/render-auth-ssh.jsx +27 -1
  10. package/client/components/bookmark-form/render-ssh-tunnel.jsx +60 -31
  11. package/client/components/bookmark-form/use-ui.jsx +12 -0
  12. package/client/components/bookmark-form/vnc-form-ui.jsx +179 -0
  13. package/client/components/bookmark-form/vnc-form.jsx +16 -0
  14. package/client/components/footer/footer-entry.jsx +13 -6
  15. package/client/components/main/term-fullscreen.styl +4 -1
  16. package/client/components/profile/profile-form-elem.jsx +87 -0
  17. package/client/components/profile/profile-form.jsx +33 -0
  18. package/client/components/profile/profile-list.jsx +79 -0
  19. package/client/components/profile/profile-transport-mod.jsx +5 -0
  20. package/client/components/profile/profile-transport.jsx +12 -0
  21. package/client/components/quick-commands/quick-command-transport-mod.jsx +13 -14
  22. package/client/components/quick-commands/quick-commands-list-form.jsx +54 -3
  23. package/client/components/rdp/rdp-session.jsx +53 -36
  24. package/client/components/session/session.jsx +14 -3
  25. package/client/components/session/session.styl +7 -2
  26. package/client/components/setting-panel/setting-modal.jsx +23 -6
  27. package/client/components/setting-panel/setting-terminal.jsx +1 -1
  28. package/client/components/setting-panel/setting-wrap.jsx +5 -1
  29. package/client/components/setting-panel/setting-wrap.styl +5 -3
  30. package/client/components/setting-panel/tab-profiles.jsx +38 -0
  31. package/client/components/sftp/list-table-ui.jsx +99 -46
  32. package/client/components/sftp/sftp-entry.jsx +1 -0
  33. package/client/components/sftp/sftp.styl +4 -1
  34. package/client/components/sftp/transfer-common.js +1 -1
  35. package/client/components/tabs/index.jsx +1 -1
  36. package/client/components/tabs/tab.jsx +9 -4
  37. package/client/components/terminal/index.jsx +5 -5
  38. package/client/components/vnc/vnc-form.jsx +66 -0
  39. package/client/components/vnc/vnc-session.jsx +297 -0
  40. package/client/store/common.js +21 -0
  41. package/client/store/index.js +1 -0
  42. package/client/store/init-state.js +4 -23
  43. package/client/store/load-data.js +9 -2
  44. package/client/store/sync.js +7 -3
  45. package/package.json +1 -1
@@ -0,0 +1,297 @@
1
+ import RdpSession from '../rdp/rdp-session'
2
+ import { createTerm } from '../terminal/terminal-apis'
3
+ import deepCopy from 'json-deep-copy'
4
+ import clone from '../../common/to-simple-obj'
5
+ import { handleErr } from '../../common/fetch'
6
+ import {
7
+ statusMap
8
+ } from '../../common/constants'
9
+ import {
10
+ Spin,
11
+ message,
12
+ Modal,
13
+ Tag
14
+ } from 'antd'
15
+ import * as ls from '../../common/safe-local-storage'
16
+ import { copy } from '../../common/clipboard'
17
+ import resolutions from '../rdp/resolutions'
18
+ import RFB from '@novnc/novnc/core/rfb'
19
+ import VncForm from './vnc-form'
20
+
21
+ const { prefix } = window
22
+ const e = prefix('form')
23
+
24
+ export default class VncSession extends RdpSession {
25
+ constructor (props) {
26
+ const id = `vnc-reso-${props.tab.host}`
27
+ const resObj = ls.getItemJSON(id, resolutions[0])
28
+ super(props)
29
+ this.state = {
30
+ types: [],
31
+ showConfirm: false,
32
+ loading: false,
33
+ aspectRatio: 4 / 3,
34
+ name: '',
35
+ ...resObj
36
+ }
37
+ }
38
+
39
+ componentDidMount () {
40
+ this.remoteInit()
41
+ }
42
+
43
+ componentWillUnmount () {
44
+ this.rfb && this.rfb.disconnect()
45
+ delete this.rfb
46
+ }
47
+
48
+ // computeProps = () => {
49
+ // const {
50
+ // height,
51
+ // width,
52
+ // tabsHeight,
53
+ // leftSidebarWidth,
54
+ // pinned,
55
+ // openedSideBar
56
+ // } = this.props
57
+ // return {
58
+ // width: width - (pinned && openedSideBar ? leftSidebarWidth : 0),
59
+ // height: height - tabsHeight
60
+ // }
61
+ // }
62
+
63
+ remoteInit = async (term = this.term) => {
64
+ this.setState({
65
+ loading: true
66
+ })
67
+ const { config } = this.props
68
+ const {
69
+ host,
70
+ port,
71
+ tokenElecterm,
72
+ server = ''
73
+ } = config
74
+ const { sessionId, id } = this.props
75
+ const tab = deepCopy(this.props.tab || {})
76
+ const {
77
+ type,
78
+ term: terminalType,
79
+ viewOnly = false,
80
+ scaleViewport = true,
81
+ username,
82
+ password
83
+ } = tab
84
+ const opts = clone({
85
+ term: terminalType || config.terminalType,
86
+ sessionId,
87
+ tabId: id,
88
+ srcTabId: tab.id,
89
+ termType: type,
90
+ ...tab
91
+ })
92
+ let pid = await createTerm(opts)
93
+ .catch(err => {
94
+ const text = err.message
95
+ handleErr({ message: text })
96
+ })
97
+ pid = pid || ''
98
+ this.setState({
99
+ loading: false
100
+ })
101
+ if (!pid) {
102
+ this.setStatus(statusMap.error)
103
+ return
104
+ }
105
+ this.setStatus(statusMap.success)
106
+ this.pid = pid
107
+ const hs = server
108
+ ? server.replace(/https?:\/\//, '')
109
+ : `${host}:${port}`
110
+ const pre = server.startsWith('https') ? 'wss' : 'ws'
111
+ const { width, height } = this.state
112
+ const wsUrl = `${pre}://${hs}/vnc/${pid}?sessionId=${sessionId}&token=${tokenElecterm}&width=${width}&height=${height}`
113
+ const vncOpts = {
114
+ scaleViewport,
115
+ viewOnly,
116
+ style: {
117
+ width: width + 'px',
118
+ height: height + 'px',
119
+ overflow: 'scroll'
120
+ },
121
+ credentials: {}
122
+ }
123
+ if (username) {
124
+ vncOpts.credentials.username = username
125
+ }
126
+ if (password) {
127
+ vncOpts.credentials.password = password
128
+ }
129
+ const rfb = new RFB(
130
+ this.getDom(),
131
+ wsUrl,
132
+ vncOpts
133
+ )
134
+ const events = [
135
+ 'connect',
136
+ 'disconnect',
137
+ 'credentialsrequired',
138
+ 'securityfailure',
139
+ 'clipboard',
140
+ 'bell',
141
+ 'desktopname',
142
+ 'capabilities'
143
+ ]
144
+ for (const event of events) {
145
+ rfb.addEventListener(event, this[`on${window.capitalizeFirstLetter(event)}`])
146
+ }
147
+ this.rfb = rfb
148
+ }
149
+
150
+ onConnect = (event) => {
151
+ // console.log('onConnect', event)
152
+ this.setStatus(statusMap.success)
153
+ this.setState({
154
+ loading: false
155
+ })
156
+ }
157
+
158
+ onDisconnect = () => {
159
+ this.setStatus(statusMap.error)
160
+ }
161
+
162
+ onSecurityfailure = (event) => {
163
+ // console.log('onSecurityFailure', event)
164
+ message.error('Security Failure: ' + event.detail?.reason)
165
+ }
166
+
167
+ onOk = (res) => {
168
+ this.setState({
169
+ showConfirm: false
170
+ })
171
+ this.rfb?.sendCredentials(res)
172
+ }
173
+
174
+ onCredentialsrequired = (event) => {
175
+ this.setState({
176
+ types: event.detail?.types || [],
177
+ showConfirm: true
178
+ })
179
+ }
180
+
181
+ renderForm (types = this.state.types) {
182
+ return (
183
+ <VncForm
184
+ types={types}
185
+ handleFinish={this.onOk}
186
+ />
187
+ )
188
+ }
189
+
190
+ onClipboard = (event) => {
191
+ // console.log('onClipboard', event)
192
+ copy(event.detail.text)
193
+ }
194
+
195
+ onBell = (event) => {
196
+ // console.log('Bell', event)
197
+ message.warning('Bell')
198
+ }
199
+
200
+ onDesktopname = (event) => {
201
+ this.setState({
202
+ name: event?.detail?.name || ''
203
+ })
204
+ }
205
+
206
+ onCapabilities = (capabilities) => {
207
+ console.log('onCapabilities', capabilities)
208
+ }
209
+
210
+ getDom = () => {
211
+ const id = 'canvas_' + this.props.tab.id
212
+ return document.getElementById(id)
213
+ }
214
+
215
+ handleReInit = () => {
216
+ this.rfb?.disconnect()
217
+ delete this.rfb
218
+ this.remoteInit()
219
+ }
220
+
221
+ renderInfo () {
222
+ const {
223
+ name
224
+ } = this.state
225
+ const {
226
+ host,
227
+ port,
228
+ username
229
+ } = this.props.tab
230
+ return (
231
+ <span className='mg2l mg2r'>
232
+ <b>{name}</b> {username}@{host}:{port}
233
+ </span>
234
+ )
235
+ }
236
+
237
+ renderHelp = () => {
238
+ return (
239
+ <Tag color='red' className='mg1l'>Beta</Tag>
240
+ )
241
+ }
242
+
243
+ renderConfirm () {
244
+ const {
245
+ showConfirm
246
+ } = this.state
247
+ if (!showConfirm) {
248
+ return null
249
+ }
250
+ const confirmProps = {
251
+ title: e('credentialsRequired'),
252
+ content: this.renderForm(['password']),
253
+ footer: null,
254
+ visible: true
255
+ }
256
+ return (
257
+ <Modal
258
+ {...confirmProps}
259
+ >
260
+ {this.renderForm()}
261
+ </Modal>
262
+ )
263
+ }
264
+
265
+ render () {
266
+ const { width: w, height: h } = this.computeProps()
267
+ const vncProps = {
268
+ style: {
269
+ width: w + 'px',
270
+ height: h + 'px'
271
+ }
272
+ }
273
+ const { width, height, loading } = this.state
274
+ const divProps = {
275
+ style: {
276
+ width: width + 'px',
277
+ height: height + 'px'
278
+ }
279
+ }
280
+ return (
281
+ <Spin spinning={loading}>
282
+ <div
283
+ {...vncProps}
284
+ className='rdp-session-wrap pd1'
285
+ >
286
+ {this.renderControl()}
287
+ <div
288
+ {...divProps}
289
+ className='vnc-session-wrap session-v-wrap'
290
+ id={'canvas_' + this.props.tab.id}
291
+ />
292
+ {this.renderConfirm()}
293
+ </div>
294
+ </Spin>
295
+ )
296
+ }
297
+ }
@@ -211,4 +211,25 @@ export default Store => {
211
211
  terminalInfos: arr
212
212
  })
213
213
  }
214
+
215
+ Store.prototype.applyProfile = function (tab) {
216
+ const {
217
+ authType,
218
+ profile
219
+ } = tab
220
+ if (authType !== 'profiles') {
221
+ return tab
222
+ }
223
+ const p = window.store.profiles.find(x => x.id === profile)
224
+ if (!p) {
225
+ return tab
226
+ }
227
+ // delete tab.password
228
+ // delete tab.privateKey
229
+ // delete tab.passphrase
230
+ return {
231
+ ...tab,
232
+ ...p
233
+ }
234
+ }
214
235
  }
@@ -282,6 +282,7 @@ const getterProps = [
282
282
  'history',
283
283
  'bookmarks',
284
284
  'bookmarkGroups',
285
+ 'profiles',
285
286
  'tabs',
286
287
  'fileTransfers',
287
288
  'transferHistory',
@@ -5,7 +5,6 @@
5
5
  import {
6
6
  settingMap,
7
7
  defaultBookmarkGroupId,
8
- newBookmarkIdPrefix,
9
8
  fileOperationsMap,
10
9
  syncTypes,
11
10
  infoTabs,
@@ -22,14 +21,12 @@ import {
22
21
  qmSortByFrequencyKey,
23
22
  resolutionsLsKey
24
23
  } from '../common/constants'
25
- import { buildDefaultThemes, buildNewTheme } from '../common/terminal-theme'
24
+ import { buildDefaultThemes } from '../common/terminal-theme'
26
25
  import * as ls from '../common/safe-local-storage'
26
+ import initSettingItem from '../common/init-setting-item'
27
27
 
28
28
  const { prefix } = window
29
29
  const t = prefix('terminalThemes')
30
- const e = prefix('common')
31
- const newQuickCommand = 'newQuickCommand'
32
- const q = prefix('quickCommands')
33
30
 
34
31
  function getDefaultBookmarkGroups (bookmarks) {
35
32
  return [
@@ -41,23 +38,6 @@ function getDefaultBookmarkGroups (bookmarks) {
41
38
  ]
42
39
  }
43
40
 
44
- export const getInitItem = (arr, tab) => {
45
- if (tab === settingMap.history) {
46
- return arr[0] || {}
47
- } else if (tab === settingMap.bookmarks) {
48
- return { id: newBookmarkIdPrefix + ':' + (Date.now()), title: '' }
49
- } else if (tab === settingMap.setting) {
50
- return { id: '', title: e('common') }
51
- } else if (tab === settingMap.terminalThemes) {
52
- return buildNewTheme()
53
- } else if (tab === settingMap.quickCommands) {
54
- return {
55
- id: '',
56
- name: q(newQuickCommand)
57
- }
58
- }
59
- }
60
-
61
41
  export default () => {
62
42
  return {
63
43
  // common
@@ -70,6 +50,7 @@ export default () => {
70
50
  termFocused: false,
71
51
  _history: '[]',
72
52
  _bookmarks: '[]',
53
+ _profiles: '[]',
73
54
  _bookmarkGroups: JSON.stringify(
74
55
  getDefaultBookmarkGroups([])
75
56
  ),
@@ -113,7 +94,7 @@ export default () => {
113
94
 
114
95
  // for settings related
115
96
  _setting: '',
116
- _settingItem: JSON.stringify(getInitItem([], settingMap.bookmarks)),
97
+ _settingItem: JSON.stringify(initSettingItem([], settingMap.bookmarks)),
117
98
  settingTab: settingMap.bookmarks, // setting tab
118
99
  autofocustrigger: Date.now(),
119
100
  bookmarkId: undefined,
@@ -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
@@ -81,6 +82,12 @@ export async function addTabFromCommandLine (store, opts) {
81
82
  log.debug('command line opts', conf)
82
83
  if (conf.username && conf.host) {
83
84
  store.addTab(conf)
85
+ } else if (
86
+ options.initFolder &&
87
+ !(store.config.onStartSessions || []).length &&
88
+ store.config.initDefaultTabOnStart
89
+ ) {
90
+ window.initFolder = options.initFolder
84
91
  }
85
92
  if (options && options.batchOp) {
86
93
  window.store.runBatchOp(options.batchOp)
@@ -170,7 +177,7 @@ export default (Store) => {
170
177
  await Promise.all(all)
171
178
  .then(arr => {
172
179
  for (const { name, data } of arr) {
173
- ext['_' + name] = data
180
+ ext['_' + name] = data || '[]'
174
181
  }
175
182
  })
176
183
  ext.lastDataUpdateTime = await getData('lastDataUpdateTime') || 0
@@ -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.5",
3
+ "version": "1.39.31",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",