@electerm/electerm-react 3.1.26 → 3.3.8

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 (61) hide show
  1. package/client/common/constants.js +1 -3
  2. package/client/common/db.js +4 -2
  3. package/client/components/ai/ai-history.jsx +4 -4
  4. package/client/components/batch-op/batch-op-alert.jsx +42 -0
  5. package/client/components/batch-op/batch-op-editor.jsx +202 -0
  6. package/client/components/batch-op/batch-op-logs.jsx +53 -0
  7. package/client/components/batch-op/batch-op-runner.jsx +315 -0
  8. package/client/components/bookmark-form/ai-bookmark-form.jsx +2 -1
  9. package/client/components/bookmark-form/bookmark-from-history-modal.jsx +2 -1
  10. package/client/components/bookmark-form/common/bookmark-select.jsx +18 -2
  11. package/client/components/bookmark-form/common/connection-hopping-form.jsx +153 -0
  12. package/client/components/bookmark-form/common/connection-hopping.jsx +136 -129
  13. package/client/components/common/auto-check-update.jsx +31 -0
  14. package/client/components/common/notification.styl +1 -1
  15. package/client/components/file-transfer/conflict-resolve.jsx +3 -0
  16. package/client/components/footer/batch-input.jsx +10 -7
  17. package/client/components/main/error-wrapper.jsx +18 -7
  18. package/client/components/main/main.jsx +6 -7
  19. package/client/components/quick-commands/qm.styl +0 -2
  20. package/client/components/quick-commands/quick-commands-list-form.jsx +1 -1
  21. package/client/components/setting-panel/hotkey.jsx +9 -1
  22. package/client/components/setting-panel/list.jsx +0 -1
  23. package/client/components/setting-panel/list.styl +4 -0
  24. package/client/components/setting-panel/setting-modal.jsx +53 -47
  25. package/client/components/setting-sync/auto-sync.jsx +53 -0
  26. package/client/components/setting-sync/data-import.jsx +69 -8
  27. package/client/components/sftp/address-bar.jsx +7 -1
  28. package/client/components/shortcuts/shortcut-editor.jsx +4 -2
  29. package/client/components/sidebar/bookmark-select.jsx +3 -2
  30. package/client/components/sidebar/history-item.jsx +3 -1
  31. package/client/components/sidebar/history.jsx +1 -0
  32. package/client/components/sidebar/index.jsx +0 -9
  33. package/client/components/tabs/add-btn-menu.jsx +1 -1
  34. package/client/components/tabs/add-btn.jsx +9 -15
  35. package/client/components/tabs/quick-connect.jsx +6 -10
  36. package/client/components/terminal/attach-addon-custom.js +86 -0
  37. package/client/components/terminal/cmd-item.jsx +13 -3
  38. package/client/components/terminal/drop-file-modal.jsx +57 -0
  39. package/client/components/terminal/terminal-command-dropdown.jsx +91 -13
  40. package/client/components/terminal/terminal.jsx +107 -10
  41. package/client/components/terminal/terminal.styl +9 -0
  42. package/client/components/tree-list/tree-list-item.jsx +0 -1
  43. package/client/components/tree-list/tree-list.jsx +115 -10
  44. package/client/components/tree-list/tree-list.styl +3 -0
  45. package/client/components/tree-list/tree-search.jsx +9 -1
  46. package/client/components/vnc/vnc-session.jsx +2 -0
  47. package/client/components/widgets/widget-control.jsx +3 -0
  48. package/client/components/widgets/widget-form.jsx +6 -0
  49. package/client/components/widgets/widget-instance.jsx +26 -7
  50. package/client/css/includes/box.styl +3 -0
  51. package/client/store/common.js +0 -28
  52. package/client/store/init-state.js +2 -1
  53. package/client/store/load-data.js +6 -4
  54. package/client/store/mcp-handler.js +20 -2
  55. package/client/store/sync.js +25 -1
  56. package/client/store/tab.js +1 -1
  57. package/client/store/watch.js +10 -18
  58. package/client/store/widgets.js +54 -0
  59. package/client/views/index.pug +1 -2
  60. package/package.json +1 -1
  61. package/client/components/batch-op/batch-op.jsx +0 -694
@@ -1,694 +0,0 @@
1
- /**
2
- * settings page
3
- */
4
-
5
- import { PureComponent } from 'react'
6
- import {
7
- CloseCircleOutlined
8
- } from '@ant-design/icons'
9
- import {
10
- Input,
11
- Button,
12
- Table,
13
- Tabs
14
- } from 'antd'
15
- import Drawer from '../common/drawer'
16
- import {
17
- sidebarWidth,
18
- statusMap,
19
- batchOpHelpLink,
20
- modals,
21
- fileActions
22
- } from '../../common/constants'
23
- import HelpIcon from '../common/help-icon'
24
- import download from '../../common/download'
25
- import { autoRun } from 'manate'
26
- import { pick } from 'lodash-es'
27
- import { runCmd } from '../terminal/terminal-apis'
28
- import deepCopy from 'json-deep-copy'
29
- import uid from '../../common/uid'
30
- import wait from '../../common/wait'
31
- import { getFolderFromFilePath } from '../sftp/file-read'
32
- import resolveFilePath from '../../common/resolve'
33
- import { refsStatic } from '../common/ref'
34
- import Upload from '../common/upload'
35
-
36
- const e = window.translate
37
-
38
- export default class BatchOp extends PureComponent {
39
- state = {
40
- text: '',
41
- loading: false,
42
- tasks: [],
43
- errors: [],
44
- history: [],
45
- working: false,
46
- tab: 'tasks'
47
- }
48
-
49
- exampleColumns = [
50
- { title: e('host'), dataIndex: 'host', key: 'host' },
51
- { title: e('port'), dataIndex: 'port', key: 'port', responsive: ['md'] },
52
- { title: e('username'), dataIndex: 'username', key: 'username', responsive: ['lg'] },
53
- { title: e('password'), dataIndex: 'password', key: 'password', responsive: ['xl'] },
54
- { title: 'Command', dataIndex: 'command', key: 'command', responsive: ['lg'] },
55
- { title: e('localPath'), dataIndex: 'localPath', key: 'localPath', responsive: ['xl'] },
56
- { title: e('remotePath'), dataIndex: 'remotePath', key: 'remotePath', responsive: ['xl'] },
57
- { title: 'Action', dataIndex: 'action', key: 'action', responsive: ['md'] },
58
- { title: 'Command After', dataIndex: 'commandAfter', key: 'commandAfter', responsive: ['xl'] }
59
- ]
60
-
61
- exampleData = [
62
- {
63
- key: '1',
64
- host: '192.168.1.3',
65
- port: '22',
66
- username: 'username',
67
- password: 'password',
68
- command: 'touch yy.js && ls -al',
69
- localPath: '/home/user/some_local_file_or_folder_to_upload',
70
- remotePath: '/server/some_server_folder_for_upload',
71
- action: 'upload',
72
- commandAfter: 'touch yy1.js && ls -al'
73
- },
74
- {
75
- key: '2',
76
- host: '192.168.1.3',
77
- port: '22',
78
- username: 'username',
79
- password: 'password',
80
- command: 'ls -al',
81
- localPath: '/home/user/some_local_folder_for_download',
82
- remotePath: '/server/some_server_file_or_folder',
83
- action: 'download',
84
- commandAfter: 'ls'
85
- }
86
- ]
87
-
88
- componentDidMount () {
89
- this.id = 'batch-op'
90
- refsStatic.add(this.id, this)
91
- }
92
-
93
- handleDownloadExample = () => {
94
- const csvText = this.exampleData.map(d => {
95
- return Object.keys(d).filter(d => d !== 'key').map(k => {
96
- return d[k]
97
- }).join(',')
98
- }).join('\n')
99
- download('batch-op-example.csv', csvText)
100
- }
101
-
102
- handleCancel = () => {
103
- window.store.toggleBatchOp()
104
- }
105
-
106
- handleChangeTab = tab => {
107
- this.setState({
108
- tab
109
- })
110
- }
111
-
112
- handleDel = rec => {
113
- this.setState(old => {
114
- return {
115
- tasks: old.tasks.filter(t => {
116
- return t.id !== rec.id
117
- })
118
- }
119
- })
120
- }
121
-
122
- handleExec = async () => {
123
- this.setState({
124
- working: true
125
- })
126
- const { tasks } = this.state
127
- const len = tasks.length
128
- for (let i = 0; i < len; i++) {
129
- await this.run(tasks[i], i)
130
- }
131
- this.setState(old => {
132
- return {
133
- working: false,
134
- history: [
135
- ...old.history,
136
- ...old.tasks
137
- ],
138
- tasks: []
139
- }
140
- })
141
- }
142
-
143
- updateState = (str, index) => {
144
- this.setState(old => {
145
- const arr = deepCopy(old.tasks)
146
- arr[index].state = str
147
- return {
148
- tasks: arr
149
- }
150
- })
151
- }
152
-
153
- run = async (conf, index) => {
154
- this.updateState('working', index)
155
- let tab = await this.createTab(conf)
156
- .catch(err => {
157
- console.log('create tab error', err)
158
- if (this.ref) {
159
- this.ref.stop()
160
- delete this.ref
161
- }
162
- return 'Error: ' + err.message
163
- })
164
- if (typeof tab === 'string') {
165
- return this.updateState(tab, index)
166
- }
167
-
168
- this.updateState('tab created', index)
169
- if (conf.cmd) {
170
- this.updateState('running cmd', index)
171
- await runCmd(tab.id, conf.cmd)
172
- this.updateState('running cmd done', index)
173
- }
174
- if (conf.remotePath) {
175
- this.updateState('creating sftp', index)
176
- tab = await this.createSftp(tab).catch(err => {
177
- console.log('create sftp error', err)
178
- if (this.ref2) {
179
- this.ref2.stop()
180
- delete this.ref2
181
- }
182
- return 'Error: ' + err.message
183
- })
184
- if (typeof tab === 'string') {
185
- return this.updateState(tab, index)
186
- }
187
- this.updateState('sftp created', index)
188
- this.updateState('transferring file', index)
189
- await this.doTransfer(tab, conf)
190
- this.updateState('done: transferring file', index)
191
- }
192
- if (conf.cmdAfterTransfer) {
193
- this.updateState('run cmd2', index)
194
- document.querySelector('.session-current .type-tab.ssh').click()
195
- await wait(200)
196
- await runCmd(tab.id, conf.cmdAfterTransfer)
197
- this.updateState('run cmd2 done', index)
198
- }
199
- this.updateState(e('finished'), index)
200
- document.querySelector('.tabs .tab.active .tab-close .anticon').click()
201
- }
202
-
203
- doTransfer = (tab, conf) => {
204
- return new Promise((resolve, reject) => {
205
- const isDown = conf.action === 'download'
206
- const fp = isDown ? conf.remotePath : conf.localPath
207
- const {
208
- name
209
- } = getFolderFromFilePath(fp)
210
- const obj = {
211
- fromPath: fp,
212
- id: uid(),
213
- operation: '',
214
- tabId: tab.id,
215
- title: 'batch operation',
216
- toPath: resolveFilePath(isDown ? conf.localPath : conf.remotePath, name),
217
- typeFrom: isDown ? 'remote' : 'local',
218
- typeTo: isDown ? 'local' : 'remote',
219
- resolvePolicy: fileActions.mergeOrOverwrite
220
- }
221
- const { store } = window
222
- store.addTransferList([obj])
223
-
224
- this.tm = setTimeout(() => {
225
- reject(new Error('timeout'))
226
- }, 1000 * 60 * 60)
227
- this.ref1 = autoRun(() => {
228
- const { transferHistory } = store
229
- const first = transferHistory.find(t => {
230
- return t.id === obj.id || t.originalId === obj.id
231
- })
232
- if (first && first.id === tab.id) {
233
- this.ref1 && this.ref1.stop()
234
- delete this.ref1
235
- clearTimeout(this.tm)
236
- resolve()
237
- }
238
- return store.transferHistory
239
- })
240
- this.ref1.start()
241
- })
242
- }
243
-
244
- createSftp = (tab) => {
245
- return new Promise((resolve, reject) => {
246
- document.querySelector('.session-current .type-tab.sftp').click()
247
- const { store } = window
248
- this.ref2 = autoRun(() => {
249
- const { tabs } = store
250
- const last = tabs.find(t => t.id === tab.id)
251
- if (
252
- last &&
253
- last.id === tab.id &&
254
- last.sftpCreated === true
255
- ) {
256
- this.ref2 && this.ref2.stop()
257
- delete this.ref2
258
- resolve(last)
259
- } else if (
260
- last &&
261
- last.id === tab.id &&
262
- last.sftpCreated === false
263
- ) {
264
- reject(new Error('failed to create sftp connection'))
265
- }
266
- return store.tabs
267
- })
268
- this.ref2.start()
269
- })
270
- }
271
-
272
- createTab = conf => {
273
- return new Promise((resolve, reject) => {
274
- const tab = {
275
- ...pick(conf, [
276
- 'host',
277
- 'port',
278
- 'username',
279
- 'password'
280
- ]),
281
- authType: 'password',
282
- enableSftp: true,
283
- enableSsh: true,
284
- encode: 'utf-8',
285
- envLang: 'en_US.UTF-8',
286
- id: uid(),
287
- title: 'batch operation',
288
- pane: 'terminal',
289
- status: 'processing',
290
- term: 'xterm-256color',
291
- x11: false,
292
- batch: window.store.batch
293
- }
294
- const { store } = window
295
- store.addTab(tab)
296
- this.ref = autoRun(() => {
297
- const { tabs } = store
298
- const len = tabs.length
299
- const last = tabs[len - 1]
300
- if (
301
- last &&
302
- last.id === tab.id &&
303
- last.status === statusMap.success
304
- ) {
305
- this.ref && this.ref.stop()
306
- delete this.ref
307
- resolve(last)
308
- } else if (last.status === statusMap.error) {
309
- reject(new Error('failed to create connection'))
310
- }
311
- return store.tabs
312
- })
313
- this.ref.start()
314
- })
315
- }
316
-
317
- handleClick = () => {
318
- this.setState({
319
- errors: [],
320
- working: false,
321
- loading: true
322
- })
323
- const reg = /,(?=(?:[^"]*"[^"]*")*[^"]*$)/g
324
- const errors = []
325
- const tasks = this.state.text
326
- .split('\n')
327
- .filter(d => d)
328
- .map(r => {
329
- const [
330
- host,
331
- port,
332
- username,
333
- password,
334
- cmd,
335
- localPath,
336
- remotePath,
337
- action,
338
- cmdAfterTransfer
339
- ] = r.split(reg).map(g => {
340
- return g.trim().replace(/^["](.*)["]$/, '$1')
341
- })
342
- if (!host || !port || !username || !password) {
343
- errors.push({
344
- text: r,
345
- msg: 'host, port, username, password required'
346
- })
347
- return ''
348
- }
349
- const res = {
350
- host,
351
- port: Number(port),
352
- username,
353
- password
354
- }
355
- if (cmd) {
356
- res.cmd = cmd
357
- }
358
- if (cmdAfterTransfer) {
359
- res.cmdAfterTransfer = cmdAfterTransfer
360
- }
361
- if (
362
- localPath &&
363
- remotePath &&
364
- action
365
- ) {
366
- Object.assign(res, {
367
- localPath,
368
- remotePath,
369
- action
370
- })
371
- }
372
- if (action && !['upload', 'download'].includes(action)) {
373
- errors.push({
374
- text: r,
375
- msg: 'action can only be download or upload'
376
- })
377
- return ''
378
- } else if (!res.cmd && !res.transfer && !res.cmdAfterTransfer) {
379
- errors.push({
380
- text: r,
381
- msg: 'must have cmd or transfer'
382
- })
383
- return ''
384
- }
385
- res.id = uid()
386
- return res
387
- })
388
- .filter(d => d)
389
- this.setState(old => {
390
- return {
391
- errors,
392
- tasks: [
393
- ...old.tasks,
394
- ...tasks
395
- ],
396
- loading: false,
397
- text: errors.length ? this.state.text : ''
398
- }
399
- })
400
- }
401
-
402
- handleChange = (e) => {
403
- this.setState({
404
- text: e.target.value
405
- })
406
- }
407
-
408
- beforeUpload = async (file) => {
409
- const filePath = file.filePath
410
- const text = await window.fs.readFile(filePath)
411
- this.setState({
412
- text
413
- })
414
- }
415
-
416
- renderError (err, i) {
417
- return (
418
- <div key={'batch-txt' + i}>
419
- <pre><code>{err.text}</code></pre>
420
- <div>{err.msg}</div>
421
- </div>
422
- )
423
- }
424
-
425
- renderErrors () {
426
- const {
427
- errors
428
- } = this.state
429
- if (!errors.length) {
430
- return null
431
- }
432
- return (
433
- <div className='pd1y'>
434
- {
435
- errors.map(this.renderError)
436
- }
437
- </div>
438
- )
439
- }
440
-
441
- renderExec () {
442
- const { length } = this.state.tasks
443
- if (!length) {
444
- return null
445
- }
446
- return (
447
- <div className='pd1b'>
448
- <Button
449
- onClick={this.handleExec}
450
- loading={this.state.working}
451
- >{e('execute')}
452
- </Button>
453
- </div>
454
- )
455
- }
456
-
457
- translate = k => {
458
- if (k === 'index') {
459
- return 'NO.'
460
- }
461
- return e(k)
462
- }
463
-
464
- renderContent () {
465
- const {
466
- text,
467
- loading,
468
- tasks,
469
- working
470
- } = this.state
471
- const disabled = loading || working
472
- const exampleTableProps = {
473
- dataSource: this.exampleData,
474
- columns: this.exampleColumns,
475
- pagination: false,
476
- size: 'small',
477
- rowKey: 'key'
478
- }
479
-
480
- return (
481
- <>
482
- <div className='pd1y'>
483
- <h2>
484
- {e('batchOperation')}
485
- <HelpIcon
486
- link={batchOpHelpLink}
487
- />
488
- </h2>
489
- <div className='pd1y'>{e('examples')}:</div>
490
- <Table
491
- {...exampleTableProps}
492
- />
493
- <div className='pd1t pd2b'>
494
- <Button
495
- onClick={this.handleDownloadExample}
496
- type='dashed'
497
- >
498
- Download example csv
499
- </Button>
500
- </div>
501
- <div className='pd2y'>
502
- <pre>
503
- <code>"192.168.1.3","22","username","password","touch yy.js && ls -al","/home/user/some_local_file_or_folder_to_upload","/server/some_server_folder_for_upload","upload","touch yy1.js && ls -al"</code>
504
- </pre>
505
- <pre>
506
- <code>"192.168.1.3","22","username","password","ls -al","/home/user/some_local_folder_for_download","/server/some_server_file_or_folder","download","ls"</code>
507
- </pre>
508
- </div>
509
- </div>
510
- {this.renderErrors()}
511
- <div className='pd1y'>
512
- <Upload
513
- beforeUpload={this.beforeUpload}
514
- fileList={[]}
515
- disabled={disabled}
516
- >
517
- <Button
518
- type='dashed'
519
- disabled={disabled}
520
- >
521
- {e('importFromCSV')}
522
- </Button>
523
- </Upload>
524
- </div>
525
- <div className='pd1y'>
526
- <Input.TextArea
527
- value={text}
528
- disabled={disabled}
529
- rows={10}
530
- onChange={this.handleChange}
531
- />
532
- </div>
533
- <div className='pd1b fix'>
534
- <div className='fleft'>
535
- <Button
536
- onClick={this.handleClick}
537
- loading={loading}
538
- disabled={disabled}
539
- htmlType='button'
540
- >{e('addToQueue')}
541
- </Button>
542
- </div>
543
- <div className='fright'>
544
- <Button
545
- onClick={this.handleExec}
546
- loading={loading}
547
- disabled={disabled || !tasks.length}
548
- type='primary'
549
- htmlType='button'
550
- >{e('execute')}
551
- </Button>
552
- </div>
553
- </div>
554
- <div className='pd1y'>
555
- {this.renderTables()}
556
- </div>
557
- </>
558
- )
559
- }
560
-
561
- renderTables () {
562
- const {
563
- tab
564
- } = this.state
565
- return (
566
- <Tabs
567
- activeKey={tab}
568
- onChange={this.handleChangeTab}
569
- items={this.renderItems()}
570
- />
571
- )
572
- }
573
-
574
- renderItems = () => {
575
- return ['tasks', 'history'].map(this.renderTab)
576
- }
577
-
578
- renderTab = (tab) => {
579
- const data = this.state[tab]
580
- const keeps = [
581
- 'index',
582
- 'host',
583
- 'state'
584
- ]
585
- const columns = [
586
- 'index',
587
- 'host',
588
- 'port',
589
- 'username',
590
- 'password',
591
- 'cmd',
592
- 'localPath',
593
- 'remotePath',
594
- 'cmdAfterTransfer',
595
- 'state'
596
- ].map(k => {
597
- return {
598
- title: this.translate(k),
599
- dataIndex: k,
600
- key: k,
601
- render: (k) => k || '',
602
- responsive: keeps.includes(k) ? undefined : ['md', 'lg', 'xl', 'xxl']
603
- }
604
- })
605
- if (tab === 'tasks') {
606
- columns.push({
607
- title: e('del'),
608
- dataIndex: 'op',
609
- key: 'op',
610
- render: (k, rec) => {
611
- if (this.state.loading || this.state.working) {
612
- return null
613
- }
614
- return (
615
- <span
616
- className='act-del pointer'
617
- onClick={() => this.handleDel(rec)}
618
- >
619
- {e('del')}
620
- </span>
621
- )
622
- }
623
- })
624
- }
625
- const src = data.map((t, i) => {
626
- return {
627
- index: i + 1,
628
- ...t
629
- }
630
- })
631
- const len = data.length
632
- const title = `${e(tab)}(${len})`
633
- return {
634
- key: tab,
635
- label: title,
636
- children: (
637
- <Table
638
- dataSource={src}
639
- columns={columns}
640
- bordered
641
- pagination={false}
642
- size='small'
643
- rowKey='id'
644
- />
645
- )
646
- }
647
- }
648
-
649
- renderClose () {
650
- const {
651
- loading,
652
- working
653
- } = this.state
654
- if (loading || working) {
655
- return null
656
- }
657
- return (
658
- <CloseCircleOutlined
659
- className='close-setting-wrap'
660
- onClick={this.handleCancel}
661
- />
662
- )
663
- }
664
-
665
- render () {
666
- const {
667
- showModal,
668
- innerWidth
669
- } = this.props
670
- const showBatchOp = showModal === modals.batchOps
671
- const pops = {
672
- open: showBatchOp,
673
- onClose: this.handleCancel,
674
- className: 'setting-wrap batch-op-wrap',
675
- size: innerWidth - sidebarWidth,
676
- zIndex: 888,
677
- placement: 'left'
678
- }
679
- return (
680
- <Drawer
681
- {...pops}
682
- >
683
- <div id='batch-op-wrap'>
684
- {this.renderClose()}
685
- <div className='setting-wrap-content'>
686
- <div className='pd3 setting-wrap-inner'>
687
- {showBatchOp ? this.renderContent() : null}
688
- </div>
689
- </div>
690
- </div>
691
- </Drawer>
692
- )
693
- }
694
- }