@electerm/electerm-react 1.80.5 → 1.80.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.
- package/client/common/default-setting.js +1 -1
- package/client/components/file-transfer/conflict-resolve.jsx +11 -1
- package/client/components/file-transfer/transfer.jsx +137 -28
- package/client/components/main/upgrade.jsx +3 -0
- package/client/components/sftp/file-icon.jsx +1 -1
- package/client/components/sftp/file-item.jsx +2 -2
- package/client/components/sftp/file-table-header.jsx +1 -1
- package/client/components/sidebar/bookmark.jsx +27 -7
- package/client/components/sidebar/info-modal.jsx +1 -1
- package/client/components/sidebar/sidebar.styl +2 -0
- package/client/components/terminal/term-search.jsx +5 -2
- package/client/components/terminal/terminal.jsx +7 -5
- package/client/entry/electerm.jsx +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ export default {
|
|
|
8
8
|
scrollback: 3000,
|
|
9
9
|
onStartSessions: [],
|
|
10
10
|
fontSize: 16,
|
|
11
|
-
fontFamily: '
|
|
11
|
+
fontFamily: 'Maple Mono, mono, courier-new, courier, monospace',
|
|
12
12
|
execWindows: 'System32/WindowsPowerShell/v1.0/powershell.exe',
|
|
13
13
|
execMac: 'zsh',
|
|
14
14
|
execLinux: 'bash',
|
|
@@ -153,7 +153,7 @@ export default class ConfirmModalStore extends Component {
|
|
|
153
153
|
<Button
|
|
154
154
|
type='dashed'
|
|
155
155
|
className='mg1l'
|
|
156
|
-
onClick={() => this.act(fileActions.
|
|
156
|
+
onClick={() => this.act(fileActions.skipAll)}
|
|
157
157
|
>
|
|
158
158
|
{e('cancel')}
|
|
159
159
|
</Button>
|
|
@@ -208,6 +208,16 @@ export default class ConfirmModalStore extends Component {
|
|
|
208
208
|
>
|
|
209
209
|
{e('renameAll')}
|
|
210
210
|
</Button>
|
|
211
|
+
<Button
|
|
212
|
+
type='primary'
|
|
213
|
+
className='mg1l'
|
|
214
|
+
title={e('skipAll')}
|
|
215
|
+
onClick={
|
|
216
|
+
() => this.act(fileActions.skipAll)
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
{e('skipAll')}
|
|
220
|
+
</Button>
|
|
211
221
|
</div>
|
|
212
222
|
</div>
|
|
213
223
|
)
|
|
@@ -28,6 +28,7 @@ export default class TransportAction extends Component {
|
|
|
28
28
|
refsTransfers.add(this.id, this)
|
|
29
29
|
this.total = 0
|
|
30
30
|
this.transferred = 0
|
|
31
|
+
this.currentProgress = 1
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
componentDidMount () {
|
|
@@ -421,11 +422,11 @@ export default class TransportAction extends Component {
|
|
|
421
422
|
}
|
|
422
423
|
this.transferred += transferred
|
|
423
424
|
const up = {}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
up.percent =
|
|
425
|
+
|
|
426
|
+
// Increment progress slightly with each file/folder (but never exceed 99%)
|
|
427
|
+
this.currentProgress = Math.min(this.currentProgress + 0.2, 99)
|
|
428
|
+
|
|
429
|
+
up.percent = Math.floor(this.currentProgress)
|
|
429
430
|
up.status = 'active'
|
|
430
431
|
up.transferred = this.transferred
|
|
431
432
|
up.startTime = this.startTime
|
|
@@ -505,49 +506,157 @@ export default class TransportAction extends Component {
|
|
|
505
506
|
return transfer
|
|
506
507
|
}
|
|
507
508
|
|
|
508
|
-
|
|
509
|
+
// Handle file transfers in parallel batches
|
|
510
|
+
transferFiles = async (files, batch, transfer) => {
|
|
511
|
+
if (this.onCancel) {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const { fromPath, toPath } = transfer
|
|
516
|
+
|
|
517
|
+
// Process files in batches
|
|
518
|
+
for (let i = 0; i < files.length; i += batch) {
|
|
519
|
+
if (this.onCancel) {
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const batchFiles = files.slice(i, i + batch)
|
|
524
|
+
const promises = batchFiles.map(file => {
|
|
525
|
+
if (this.onCancel) {
|
|
526
|
+
return Promise.resolve(0)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const fromItemPath = resolve(fromPath, file.name)
|
|
530
|
+
const toItemPath = resolve(toPath, file.name)
|
|
531
|
+
|
|
532
|
+
const itemTransfer = {
|
|
533
|
+
...transfer,
|
|
534
|
+
fromPath: fromItemPath,
|
|
535
|
+
toPath: toItemPath,
|
|
536
|
+
fromFile: file
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return this.transferFileAsSubTransfer(itemTransfer)
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
// Wait for all files in batch to complete
|
|
543
|
+
const results = await Promise.all(promises)
|
|
544
|
+
|
|
545
|
+
// Update progress once for the entire batch
|
|
546
|
+
const batchTotalSize = results.reduce((sum, size) => sum + size, 0)
|
|
547
|
+
if (batchTotalSize > 0) {
|
|
548
|
+
this.onFolderData(batchTotalSize)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Handle folder transfers sequentially to prevent concurrency explosion
|
|
554
|
+
transferFolders = async (folders, batch, transfer) => {
|
|
555
|
+
if (this.onCancel) {
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const { fromPath, toPath } = transfer
|
|
560
|
+
|
|
561
|
+
// Step 1: Create all folders concurrently in batches
|
|
562
|
+
for (let i = 0; i < folders.length; i += batch) {
|
|
563
|
+
if (this.onCancel) {
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const batchFolders = folders.slice(i, i + batch)
|
|
568
|
+
const createFolderPromises = batchFolders.map(folder => {
|
|
569
|
+
const toItemPath = resolve(toPath, folder.name)
|
|
570
|
+
|
|
571
|
+
// Create folder itself (don't process contents)
|
|
572
|
+
const createTransfer = {
|
|
573
|
+
...transfer,
|
|
574
|
+
toPath: toItemPath,
|
|
575
|
+
fromFile: folder
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return this.mkdir(createTransfer)
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// Create all folders in this batch concurrently
|
|
582
|
+
await Promise.all(createFolderPromises)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Step 2: Process contents of each folder sequentially
|
|
586
|
+
for (const folder of folders) {
|
|
587
|
+
if (this.onCancel) {
|
|
588
|
+
return
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const fromItemPath = resolve(fromPath, folder.name)
|
|
592
|
+
const toItemPath = resolve(toPath, folder.name)
|
|
593
|
+
|
|
594
|
+
const itemTransfer = {
|
|
595
|
+
...transfer,
|
|
596
|
+
fromPath: fromItemPath,
|
|
597
|
+
toPath: toItemPath,
|
|
598
|
+
fromFile: folder
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Transfer folder contents (set createFolder = false since we already created it)
|
|
602
|
+
await this.transferFolderRecursive(itemTransfer, false)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Main recursive function using the separate handlers
|
|
607
|
+
transferFolderRecursive = async (transfer = this.getDefaultTransfer(), createFolder = true) => {
|
|
509
608
|
if (this.onCancel) {
|
|
510
609
|
return
|
|
511
610
|
}
|
|
512
611
|
const {
|
|
513
612
|
fromPath,
|
|
514
|
-
toPath,
|
|
515
613
|
typeFrom,
|
|
516
|
-
typeTo,
|
|
517
614
|
sessionId,
|
|
518
615
|
toFile,
|
|
519
616
|
isRenamed
|
|
520
617
|
} = transfer
|
|
521
|
-
|
|
618
|
+
|
|
619
|
+
if (createFolder && (!toFile || isRenamed)) {
|
|
522
620
|
const folderCreated = await this.mkdir(transfer)
|
|
523
621
|
if (!folderCreated) {
|
|
524
622
|
return
|
|
525
623
|
}
|
|
526
624
|
}
|
|
625
|
+
|
|
527
626
|
const list = await this.list(typeFrom, fromPath, sessionId)
|
|
627
|
+
const bigFileSize = 1024 * 1024
|
|
628
|
+
const smallFilesBatch = 30
|
|
629
|
+
const BigFilesBatch = 3
|
|
630
|
+
const foldersBatch = 50
|
|
528
631
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
632
|
+
const {
|
|
633
|
+
folders,
|
|
634
|
+
smallFiles,
|
|
635
|
+
largeFiles
|
|
636
|
+
} = list.reduce((p, c) => {
|
|
637
|
+
if (c.isDirectory) {
|
|
638
|
+
p.folders.push(c)
|
|
639
|
+
} else {
|
|
640
|
+
this.total += c.size
|
|
641
|
+
if (c.size < bigFileSize) {
|
|
642
|
+
p.smallFiles.push(c)
|
|
643
|
+
} else {
|
|
644
|
+
p.largeFiles.push(c)
|
|
645
|
+
}
|
|
532
646
|
}
|
|
533
|
-
|
|
534
|
-
|
|
647
|
+
return p
|
|
648
|
+
}, {
|
|
649
|
+
folders: [],
|
|
650
|
+
smallFiles: [],
|
|
651
|
+
largeFiles: []
|
|
652
|
+
})
|
|
535
653
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
toPath: toItemPath,
|
|
540
|
-
fromFile: item
|
|
541
|
-
}
|
|
654
|
+
// Process files with parallel batching
|
|
655
|
+
await this.transferFiles(smallFiles, smallFilesBatch, transfer)
|
|
656
|
+
await this.transferFiles(largeFiles, BigFilesBatch, transfer)
|
|
542
657
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (item.isDirectory) {
|
|
546
|
-
await this.transferFolderRecursive(itemTransfer)
|
|
547
|
-
} else {
|
|
548
|
-
await this.transferFileAsSubTransfer(itemTransfer)
|
|
549
|
-
}
|
|
550
|
-
}
|
|
658
|
+
// Process folders sequentially
|
|
659
|
+
await this.transferFolders(folders, foldersBatch, transfer)
|
|
551
660
|
}
|
|
552
661
|
|
|
553
662
|
onError = (e) => {
|
|
@@ -671,7 +671,7 @@ export default class FileSection extends React.Component {
|
|
|
671
671
|
const {
|
|
672
672
|
path, name
|
|
673
673
|
} = this.state.file
|
|
674
|
-
const rp = resolve(path, name)
|
|
674
|
+
const rp = path ? resolve(path, name) : this.props[`${this.props.type}Path`]
|
|
675
675
|
this.props.tab.pane = paneMap.terminal
|
|
676
676
|
refs.get('term-' + this.props.tab.id)?.cd(rp)
|
|
677
677
|
}
|
|
@@ -953,7 +953,7 @@ export default class FileSection extends React.Component {
|
|
|
953
953
|
})
|
|
954
954
|
}
|
|
955
955
|
if (
|
|
956
|
-
isDirectory &&
|
|
956
|
+
isDirectory &&
|
|
957
957
|
(
|
|
958
958
|
(hasHost && enableSsh !== false && isRemote) ||
|
|
959
959
|
(isLocal && !hasHost)
|
|
@@ -49,7 +49,7 @@ export default class FileListTableHeader extends Component {
|
|
|
49
49
|
}
|
|
50
50
|
const text = e(id || '')
|
|
51
51
|
const directionIcon = isSorting
|
|
52
|
-
? (sortDirection === 'asc' ? <
|
|
52
|
+
? (sortDirection === 'asc' ? <UpOutlined /> : <DownOutlined />)
|
|
53
53
|
: null
|
|
54
54
|
const itemProps = {
|
|
55
55
|
onClick: this.props.onClickName,
|
|
@@ -1,15 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
|
|
1
|
+
import { refsStatic } from '../common/ref'
|
|
2
|
+
import { useEffect, useRef } from 'react'
|
|
5
3
|
import BookmarkSelect from './bookmark-select'
|
|
4
|
+
import { debounce } from 'lodash-es'
|
|
6
5
|
|
|
7
6
|
export default function BookmarkPanel (props) {
|
|
8
7
|
const { store } = window
|
|
8
|
+
const bookmarksPanelRef = useRef(null)
|
|
9
|
+
const SCROLL_REF_ID = 'bookmarks-scroll-position'
|
|
10
|
+
|
|
11
|
+
// On component mount, restore scroll position
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (store.openedSideBar) {
|
|
14
|
+
const savedPosition = refsStatic.get(SCROLL_REF_ID)
|
|
15
|
+
if (savedPosition) {
|
|
16
|
+
setTimeout(() => {
|
|
17
|
+
bookmarksPanelRef.current.scrollTop = savedPosition
|
|
18
|
+
}, 100)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}, [store.openedSideBar])
|
|
22
|
+
|
|
23
|
+
// Save scroll position when scrolling
|
|
24
|
+
const handleScroll = debounce((e) => {
|
|
25
|
+
const top = e.target.scrollTop
|
|
26
|
+
if (top > 0) {
|
|
27
|
+
refsStatic.add(SCROLL_REF_ID, e.target.scrollTop)
|
|
28
|
+
}
|
|
29
|
+
}, 100)
|
|
30
|
+
|
|
9
31
|
return (
|
|
10
|
-
<div
|
|
11
|
-
className='sidebar-panel-bookmarks'
|
|
12
|
-
>
|
|
32
|
+
<div className='sidebar-panel-bookmarks' ref={bookmarksPanelRef} onScroll={handleScroll}>
|
|
13
33
|
<div className='pd2l sidebar-inner'>
|
|
14
34
|
<BookmarkSelect store={store} from='sidebar' />
|
|
15
35
|
</div>
|
|
@@ -32,7 +32,7 @@ export default auto(function InfoModal (props) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const renderCheckUpdate = () => {
|
|
35
|
-
if (srcsSkipUpgradeCheck.includes(props.installSrc)) {
|
|
35
|
+
if (window.et.isWebApp || srcsSkipUpgradeCheck.includes(props.installSrc)) {
|
|
36
36
|
return null
|
|
37
37
|
}
|
|
38
38
|
const {
|
|
@@ -53,11 +53,14 @@ export default class TermSearch extends PureComponent {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
toggleSearch = () => {
|
|
56
|
-
|
|
56
|
+
const isClosing = this.props.termSearchOpen
|
|
57
|
+
if (isClosing) {
|
|
57
58
|
this.clearSearch()
|
|
58
59
|
}
|
|
59
60
|
window.store.toggleTerminalSearch()
|
|
60
|
-
|
|
61
|
+
if (isClosing) {
|
|
62
|
+
setTimeout(window.store.focus, 200)
|
|
63
|
+
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
prev = (v = this.props.termSearch) => {
|
|
@@ -336,6 +336,7 @@ clear\r`
|
|
|
336
336
|
try {
|
|
337
337
|
const fileData = JSON.parse(fromFile)
|
|
338
338
|
const filePath = resolve(fileData.path, fileData.name)
|
|
339
|
+
console.log('filePath', filePath)
|
|
339
340
|
if (this.isUnsafeFilename(filePath)) {
|
|
340
341
|
message.error(notSafeMsg)
|
|
341
342
|
return
|
|
@@ -350,14 +351,15 @@ clear\r`
|
|
|
350
351
|
// Handle regular file drop
|
|
351
352
|
const files = dt.files
|
|
352
353
|
if (files && files.length) {
|
|
353
|
-
const
|
|
354
|
-
|
|
354
|
+
const arr = Array.from(files)
|
|
355
|
+
// Check each file path individually
|
|
356
|
+
const hasUnsafeFilename = arr.some(f => this.isUnsafeFilename(f.path))
|
|
357
|
+
if (hasUnsafeFilename) {
|
|
355
358
|
message.error(notSafeMsg)
|
|
356
359
|
return
|
|
357
360
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
)
|
|
361
|
+
const filesAll = arr.map(f => `"${f.path}"`).join(' ')
|
|
362
|
+
this.attachAddon._sendData(filesAll)
|
|
361
363
|
}
|
|
362
364
|
}
|
|
363
365
|
|
|
@@ -2,7 +2,7 @@ import { createRoot } from 'react-dom/client'
|
|
|
2
2
|
import 'antd/dist/reset.css'
|
|
3
3
|
import '@xterm/xterm/css/xterm.css'
|
|
4
4
|
import '../common/trzsz.js'
|
|
5
|
-
import '
|
|
5
|
+
import '@fontsource/maple-mono/index.css'
|
|
6
6
|
import Main from '../components/main/index.jsx'
|
|
7
7
|
|
|
8
8
|
const rootElement = createRoot(document.getElementById('container'))
|