@electerm/electerm-react 3.15.198 → 5.0.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.
|
@@ -10,7 +10,6 @@ import { isEqual, last, isNumber, some, isArray, pick, uniq, debounce } from 'lo
|
|
|
10
10
|
import FileSection from './file-item'
|
|
11
11
|
import resolve from '../../common/resolve'
|
|
12
12
|
import wait from '../../common/wait'
|
|
13
|
-
import isAbsPath from '../../common/is-absolute-path'
|
|
14
13
|
import classnames from 'classnames'
|
|
15
14
|
import sorterIndex from '../../common/index-sorter'
|
|
16
15
|
import { handleErr } from '../../common/fetch'
|
|
@@ -107,8 +106,6 @@ export default class Sftp extends Component {
|
|
|
107
106
|
this.sftp = null
|
|
108
107
|
clearTimeout(this.timer4)
|
|
109
108
|
this.timer4 = null
|
|
110
|
-
clearTimeout(this.timer5)
|
|
111
|
-
this.timer5 = null
|
|
112
109
|
// Clear sort cache to prevent memory leaks
|
|
113
110
|
this._sortCache?.clear()
|
|
114
111
|
this._lastSortArgs = null
|
|
@@ -455,9 +452,7 @@ export default class Sftp extends Component {
|
|
|
455
452
|
}
|
|
456
453
|
const type = files[0]?.type || _type
|
|
457
454
|
const func = this[type + 'Del']
|
|
458
|
-
|
|
459
|
-
await func(f)
|
|
460
|
-
}
|
|
455
|
+
await Promise.all(files.map(f => func(f)))
|
|
461
456
|
if (type === typeMap.remote) {
|
|
462
457
|
await wait(500)
|
|
463
458
|
}
|
|
@@ -560,8 +555,10 @@ export default class Sftp extends Component {
|
|
|
560
555
|
}
|
|
561
556
|
|
|
562
557
|
initRemoteAll = async () => {
|
|
563
|
-
await
|
|
564
|
-
|
|
558
|
+
await Promise.all([
|
|
559
|
+
this.remoteList(),
|
|
560
|
+
this.remoteListOwner().catch(e => console.debug('remoteListOwner error:', e))
|
|
561
|
+
])
|
|
565
562
|
}
|
|
566
563
|
|
|
567
564
|
modifier = (...args) => {
|
|
@@ -647,12 +644,10 @@ export default class Sftp extends Component {
|
|
|
647
644
|
}
|
|
648
645
|
|
|
649
646
|
remoteListOwner = async () => {
|
|
650
|
-
const remoteUidTree = await
|
|
651
|
-
this.props.pid
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
this.props.pid
|
|
655
|
-
)
|
|
647
|
+
const [remoteUidTree, remoteGidTree] = await Promise.all([
|
|
648
|
+
owner.remoteListUsers(this.props.pid),
|
|
649
|
+
owner.remoteListGroups(this.props.pid)
|
|
650
|
+
])
|
|
656
651
|
this.setState({
|
|
657
652
|
remoteGidTree,
|
|
658
653
|
remoteUidTree
|
|
@@ -660,8 +655,10 @@ export default class Sftp extends Component {
|
|
|
660
655
|
}
|
|
661
656
|
|
|
662
657
|
localListOwner = async () => {
|
|
663
|
-
const localUidTree = await
|
|
664
|
-
|
|
658
|
+
const [localUidTree, localGidTree] = await Promise.all([
|
|
659
|
+
owner.localListUsers(),
|
|
660
|
+
owner.localListGroups()
|
|
661
|
+
])
|
|
665
662
|
this.setState({
|
|
666
663
|
localGidTree,
|
|
667
664
|
localUidTree
|
|
@@ -800,14 +797,6 @@ export default class Sftp extends Component {
|
|
|
800
797
|
sftpCreated: true
|
|
801
798
|
})
|
|
802
799
|
})
|
|
803
|
-
this.timer5 = setTimeout(() => {
|
|
804
|
-
if (this.type !== 'ftp') {
|
|
805
|
-
this.updateRemoteList(remote, remotePath, sftp)
|
|
806
|
-
}
|
|
807
|
-
this.props.editTab(tab.id, {
|
|
808
|
-
sftpCreated: true
|
|
809
|
-
})
|
|
810
|
-
}, 1000)
|
|
811
800
|
} catch (e) {
|
|
812
801
|
const update = {
|
|
813
802
|
remoteLoading: false,
|
|
@@ -828,41 +817,34 @@ export default class Sftp extends Component {
|
|
|
828
817
|
remotePath,
|
|
829
818
|
sftp
|
|
830
819
|
) => {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
})
|
|
841
|
-
if (!realpath) {
|
|
842
|
-
continue
|
|
843
|
-
}
|
|
844
|
-
if (!isAbsPath(realpath)) {
|
|
845
|
-
realpath = resolve(remotePath, realpath)
|
|
846
|
-
realpath = await sftp.realpath(realpath)
|
|
820
|
+
// Resolve all symlinks in parallel for faster updates.
|
|
821
|
+
// Use sftp.stat (which follows symlinks) directly on the link path
|
|
822
|
+
// instead of readlink + realpath + stat, reducing 2-3 SFTP round
|
|
823
|
+
// trips per symlink to just 1.
|
|
824
|
+
const results = await Promise.all(
|
|
825
|
+
remotes.map(async r => {
|
|
826
|
+
if (!r.isSymbol) {
|
|
827
|
+
r.isSymbolicLink = false
|
|
828
|
+
return r
|
|
847
829
|
}
|
|
830
|
+
const linkPath = resolve(remotePath, r.name)
|
|
848
831
|
const realFileInfo = await getRemoteFileInfo(
|
|
849
832
|
sftp,
|
|
850
|
-
|
|
833
|
+
linkPath
|
|
851
834
|
).catch(e => {
|
|
852
835
|
console.debug('seems a bad symbolic link')
|
|
853
836
|
console.debug(e)
|
|
854
837
|
return null
|
|
855
838
|
})
|
|
856
839
|
if (!realFileInfo) {
|
|
857
|
-
|
|
840
|
+
return null
|
|
858
841
|
}
|
|
859
842
|
r.isSymbolicLink = true
|
|
860
843
|
r.isDirectory = realFileInfo.isDirectory
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
844
|
+
return r
|
|
845
|
+
})
|
|
846
|
+
)
|
|
847
|
+
const remote = results.filter(Boolean)
|
|
866
848
|
const update = {
|
|
867
849
|
remote,
|
|
868
850
|
remoteFileTree: this.buildTree(remote, typeMap.remote)
|
|
@@ -892,14 +874,13 @@ export default class Sftp extends Component {
|
|
|
892
874
|
this.getCwdLocal() ||
|
|
893
875
|
this.getLocalHome()
|
|
894
876
|
const locals = await window.fs.readdirAsync(localPath)
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
}
|
|
877
|
+
const results = await Promise.all(
|
|
878
|
+
locals.map(name => {
|
|
879
|
+
const p = resolve(localPath, name)
|
|
880
|
+
return getLocalFileInfo(p).catch(console.log)
|
|
881
|
+
})
|
|
882
|
+
)
|
|
883
|
+
const local = results.filter(Boolean)
|
|
903
884
|
const update = {
|
|
904
885
|
local,
|
|
905
886
|
inited: true,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '@ant-design/icons'
|
|
16
16
|
import message from '../common/message'
|
|
17
17
|
import Modal from '../common/modal'
|
|
18
|
-
import { copy } from '../../common/clipboard'
|
|
18
|
+
import { copy, readClipboard } from '../../common/clipboard'
|
|
19
19
|
import VncForm from './vnc-form'
|
|
20
20
|
import RemoteFloatControl from '../common/remote-float-control'
|
|
21
21
|
import './vnc.styl'
|
|
@@ -49,11 +49,16 @@ export default class VncSession extends PureComponent {
|
|
|
49
49
|
|
|
50
50
|
domRef = createRef()
|
|
51
51
|
|
|
52
|
+
// clipboard sync state (local -> remote direction)
|
|
53
|
+
lastClip = null
|
|
54
|
+
vncViewOnly = false
|
|
55
|
+
|
|
52
56
|
componentDidMount () {
|
|
53
57
|
this.remoteInit()
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
componentWillUnmount () {
|
|
61
|
+
this._detachClipboardSync()
|
|
57
62
|
this.rfb && this.rfb.disconnect()
|
|
58
63
|
delete this.rfb
|
|
59
64
|
clearTimeout(this.timer)
|
|
@@ -379,6 +384,7 @@ export default class VncSession extends PureComponent {
|
|
|
379
384
|
rfb.compressionLevel = compressionLevel
|
|
380
385
|
rfb.viewOnly = viewOnly
|
|
381
386
|
rfb.showDotCursor = showDotCursor
|
|
387
|
+
this.vncViewOnly = viewOnly
|
|
382
388
|
this.rfb = rfb
|
|
383
389
|
}
|
|
384
390
|
|
|
@@ -388,12 +394,14 @@ export default class VncSession extends PureComponent {
|
|
|
388
394
|
this.setState({
|
|
389
395
|
loading: false
|
|
390
396
|
})
|
|
397
|
+
this._attachClipboardSync()
|
|
391
398
|
}
|
|
392
399
|
|
|
393
400
|
onDesktopsize = (event) => {
|
|
394
401
|
}
|
|
395
402
|
|
|
396
403
|
onDisconnect = () => {
|
|
404
|
+
this._detachClipboardSync()
|
|
397
405
|
this.setStatus(statusMap.error)
|
|
398
406
|
}
|
|
399
407
|
|
|
@@ -433,7 +441,61 @@ export default class VncSession extends PureComponent {
|
|
|
433
441
|
}
|
|
434
442
|
|
|
435
443
|
onClipboard = (event) => {
|
|
436
|
-
|
|
444
|
+
const text = event?.detail?.text
|
|
445
|
+
if (!text || text === this.lastClip) {
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
// Track so our click-driven sync won't echo this same text back to the
|
|
449
|
+
// server (some servers re-broadcast what they receive).
|
|
450
|
+
this.lastClip = text
|
|
451
|
+
copy(text)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Push the current local clipboard to the remote when it has changed.
|
|
455
|
+
// This implements the local -> remote direction that noVNC cannot do itself.
|
|
456
|
+
// Triggered on view click instead of polling, so it only runs right when the
|
|
457
|
+
// user is about to interact with the remote desktop (e.g. before pasting).
|
|
458
|
+
handleViewMouseDown = () => {
|
|
459
|
+
const rfb = this.rfb
|
|
460
|
+
if (!rfb || this.vncViewOnly) {
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
let text
|
|
464
|
+
try {
|
|
465
|
+
text = readClipboard()
|
|
466
|
+
} catch (e) {
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
if (!text || text === this.lastClip) {
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
this.lastClip = text
|
|
473
|
+
try {
|
|
474
|
+
rfb.clipboardPasteFrom(text)
|
|
475
|
+
} catch (e) {
|
|
476
|
+
// clipboardPasteFrom can throw if the socket is in a bad state; ignore
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// noVNC calls stopPropagation() on canvas mouse events in the bubble phase,
|
|
481
|
+
// so React's delegated onClick (or any bubble-phase listener) never fires.
|
|
482
|
+
// We attach a CAPTURE-phase mousedown listener on the wrapper so it runs
|
|
483
|
+
// before noVNC can stop propagation. mousedown also fires earlier than click,
|
|
484
|
+
// giving the remote clipboard time to update before the user pastes.
|
|
485
|
+
_attachClipboardSync = () => {
|
|
486
|
+
this._detachClipboardSync()
|
|
487
|
+
const el = this.domRef.current
|
|
488
|
+
if (!el) {
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
el.addEventListener('mousedown', this.handleViewMouseDown, true)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
_detachClipboardSync = () => {
|
|
495
|
+
const el = this.domRef.current
|
|
496
|
+
if (el) {
|
|
497
|
+
el.removeEventListener('mousedown', this.handleViewMouseDown, true)
|
|
498
|
+
}
|
|
437
499
|
}
|
|
438
500
|
|
|
439
501
|
onBell = (event) => {
|
|
@@ -542,6 +604,7 @@ export default class VncSession extends PureComponent {
|
|
|
542
604
|
}
|
|
543
605
|
|
|
544
606
|
handleReInit = () => {
|
|
607
|
+
this._detachClipboardSync()
|
|
545
608
|
this.rfb?.disconnect()
|
|
546
609
|
delete this.rfb
|
|
547
610
|
this.remoteInit()
|