@electerm/electerm-react 2.3.103 → 2.3.113
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/constants.js +1 -0
- package/client/components/bookmark-form/config/vnc.js +2 -0
- package/client/components/file-transfer/transfer.jsx +3 -1
- package/client/components/layout/layout.jsx +1 -0
- package/client/components/main/main.jsx +1 -1
- package/client/components/setting-panel/deep-link-control.jsx +138 -0
- package/client/components/setting-panel/setting-common.jsx +2 -0
- package/client/components/sftp/file-item.jsx +1 -10
- package/client/components/tabs/add-btn-menu.jsx +89 -0
- package/client/components/tabs/add-btn.jsx +16 -38
- package/client/components/tabs/add-btn.styl +6 -2
- package/client/components/tabs/index.jsx +1 -0
- package/client/components/tabs/tabs.styl +0 -2
- package/client/components/vnc/vnc-session.jsx +3 -0
- package/client/store/common.js +6 -0
- package/client/store/init-state.js +2 -0
- package/client/store/load-data.js +9 -1
- package/package.json +1 -1
|
@@ -145,6 +145,7 @@ export const openedSidebarKey = 'opened-sidebar'
|
|
|
145
145
|
export const sidebarPinnedKey = 'sidebar-pinned'
|
|
146
146
|
export const leftSidebarWidthKey = 'left-sidebar-width'
|
|
147
147
|
export const rightSidebarWidthKey = 'right-sidebar-width'
|
|
148
|
+
export const addPanelWidthLsKey = 'addPanelWidth'
|
|
148
149
|
export const sftpDefaultSortSettingKey = 'sftp-default-sort'
|
|
149
150
|
export const qmSortByFrequencyKey = 'qm-sort-by-frequency'
|
|
150
151
|
|
|
@@ -13,6 +13,7 @@ const vncConfig = {
|
|
|
13
13
|
return createBaseInitValues(props, terminalVncType, {
|
|
14
14
|
port: 5900,
|
|
15
15
|
viewOnly: false,
|
|
16
|
+
clipViewport: true,
|
|
16
17
|
scaleViewport: true,
|
|
17
18
|
connectionHoppings: [],
|
|
18
19
|
...getAuthTypeDefault(props)
|
|
@@ -30,6 +31,7 @@ const vncConfig = {
|
|
|
30
31
|
{ type: 'input', name: 'host', label: e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
|
|
31
32
|
commonFields.port,
|
|
32
33
|
{ type: 'switch', name: 'viewOnly', label: e('viewOnly'), valuePropName: 'checked' },
|
|
34
|
+
{ type: 'switch', name: 'clipViewport', label: e('clipViewport'), valuePropName: 'checked' },
|
|
33
35
|
{ type: 'switch', name: 'scaleViewport', label: e('scaleViewport'), valuePropName: 'checked' },
|
|
34
36
|
{ type: 'profileItem', name: '__profile__', label: '', profileFilter: d => !isEmpty(d.vnc) },
|
|
35
37
|
commonFields.username,
|
|
@@ -512,7 +512,9 @@ export default class TransportAction extends Component {
|
|
|
512
512
|
|
|
513
513
|
startTransfer = async () => {
|
|
514
514
|
const { fromFile = this.fromFile, zip } = this.props.transfer
|
|
515
|
-
|
|
515
|
+
if (!fromFile) {
|
|
516
|
+
return
|
|
517
|
+
}
|
|
516
518
|
if (!fromFile.isDirectory) {
|
|
517
519
|
return this.transferFile()
|
|
518
520
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { Button, message, Tooltip, Tag, Space } from 'antd'
|
|
3
|
+
import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'
|
|
4
|
+
|
|
5
|
+
const e = window.translate
|
|
6
|
+
|
|
7
|
+
export default function DeepLinkControl () {
|
|
8
|
+
const [loading, setLoading] = useState(false)
|
|
9
|
+
const [registrationStatus, setRegistrationStatus] = useState(null)
|
|
10
|
+
|
|
11
|
+
const checkRegistrationStatus = async () => {
|
|
12
|
+
try {
|
|
13
|
+
const status = await window.pre.runGlobalAsync('checkProtocolRegistration')
|
|
14
|
+
setRegistrationStatus(status)
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error('Failed to check protocol registration:', error)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
checkRegistrationStatus()
|
|
22
|
+
}, [])
|
|
23
|
+
|
|
24
|
+
const handleRegister = async () => {
|
|
25
|
+
setLoading(true)
|
|
26
|
+
try {
|
|
27
|
+
const result = await window.pre.runGlobalAsync('registerDeepLink', true)
|
|
28
|
+
if (result.registered) {
|
|
29
|
+
message.success('Protocol handlers registered successfully')
|
|
30
|
+
await checkRegistrationStatus()
|
|
31
|
+
} else {
|
|
32
|
+
message.warning(e('deepLinkSkipped') || 'Registration skipped: ' + result.reason)
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
message.error('Failed to register protocol handlers')
|
|
36
|
+
console.error('Registration error:', error)
|
|
37
|
+
} finally {
|
|
38
|
+
setLoading(false)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const handleUnregister = async () => {
|
|
43
|
+
setLoading(true)
|
|
44
|
+
try {
|
|
45
|
+
await window.pre.runGlobalAsync('unregisterDeepLink')
|
|
46
|
+
message.success('Protocol handlers unregistered successfully')
|
|
47
|
+
await checkRegistrationStatus()
|
|
48
|
+
} catch (error) {
|
|
49
|
+
message.error('Failed to unregister protocol handlers')
|
|
50
|
+
console.error('Unregistration error:', error)
|
|
51
|
+
} finally {
|
|
52
|
+
setLoading(false)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const isAnyProtocolRegistered = () => {
|
|
57
|
+
if (!registrationStatus) return false
|
|
58
|
+
return Object.values(registrationStatus).some(status => status === true)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const isAllProtocolsRegistered = () => {
|
|
62
|
+
if (!registrationStatus) return false
|
|
63
|
+
return Object.values(registrationStatus).every(status => status === true)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const renderTooltipContent = () => {
|
|
67
|
+
const protocols = ['ssh', 'telnet', 'rdp', 'vnc', 'serial']
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
<div className='pd1b'>
|
|
72
|
+
Register electerm to handle protocol URLs (ssh://, telnet://, rdp://, vnc://, serial://)
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{registrationStatus && (
|
|
76
|
+
<>
|
|
77
|
+
<div className='pd1b'>
|
|
78
|
+
Protocol Status
|
|
79
|
+
</div>
|
|
80
|
+
<div className='pd1b'>
|
|
81
|
+
<Space size='small' wrap>
|
|
82
|
+
{protocols.map(protocol => {
|
|
83
|
+
const isRegistered = registrationStatus[protocol]
|
|
84
|
+
return (
|
|
85
|
+
<Tag
|
|
86
|
+
key={protocol}
|
|
87
|
+
icon={isRegistered ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
|
|
88
|
+
color={isRegistered ? 'success' : 'default'}
|
|
89
|
+
>
|
|
90
|
+
{protocol}://
|
|
91
|
+
</Tag>
|
|
92
|
+
)
|
|
93
|
+
})}
|
|
94
|
+
</Space>
|
|
95
|
+
</div>
|
|
96
|
+
</>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const isRegistered = isAnyProtocolRegistered()
|
|
103
|
+
const isAllRegistered = isAllProtocolsRegistered()
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className='pd2b'>
|
|
107
|
+
<Tooltip
|
|
108
|
+
title={renderTooltipContent()}
|
|
109
|
+
>
|
|
110
|
+
<Space>
|
|
111
|
+
{
|
|
112
|
+
!isAllRegistered && (
|
|
113
|
+
<Button
|
|
114
|
+
type='primary'
|
|
115
|
+
onClick={handleRegister}
|
|
116
|
+
loading={loading}
|
|
117
|
+
>
|
|
118
|
+
{e('registerDeepLink')}
|
|
119
|
+
</Button>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
{
|
|
123
|
+
isRegistered && (
|
|
124
|
+
<Button
|
|
125
|
+
color='danger'
|
|
126
|
+
variant='solid'
|
|
127
|
+
onClick={handleUnregister}
|
|
128
|
+
loading={loading}
|
|
129
|
+
>
|
|
130
|
+
{e('unregisterDeepLink')}
|
|
131
|
+
</Button>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
</Space>
|
|
135
|
+
</Tooltip>
|
|
136
|
+
</div>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
@@ -31,6 +31,7 @@ import StartSession from './start-session-select'
|
|
|
31
31
|
import HelpIcon from '../common/help-icon'
|
|
32
32
|
import delay from '../../common/wait.js'
|
|
33
33
|
import isColorDark from '../../common/is-color-dark'
|
|
34
|
+
import DeepLinkControl from './deep-link-control'
|
|
34
35
|
import './setting.styl'
|
|
35
36
|
|
|
36
37
|
const { Option } = Select
|
|
@@ -675,6 +676,7 @@ export default class SettingCommon extends Component {
|
|
|
675
676
|
'debug'
|
|
676
677
|
].map(this.renderToggle)
|
|
677
678
|
}
|
|
679
|
+
<DeepLinkControl />
|
|
678
680
|
{this.renderLoginPass()}
|
|
679
681
|
{this.renderReset()}
|
|
680
682
|
</div>
|
|
@@ -271,16 +271,7 @@ export default class FileSection extends React.Component {
|
|
|
271
271
|
document.querySelectorAll('.' + onDragOverCls).forEach((d) => {
|
|
272
272
|
removeClass(d, onDragOverCls)
|
|
273
273
|
})
|
|
274
|
-
|
|
275
|
-
const dt = e.dataTransfer
|
|
276
|
-
if (dt.items) {
|
|
277
|
-
// Use DataTransferItemList interface to remove the drag data
|
|
278
|
-
for (let i = 0, len = dt.items.length; i < len; i++) {
|
|
279
|
-
dt.items.remove(i)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
dt.clearData()
|
|
283
|
-
}
|
|
274
|
+
e && e.dataTransfer && e.dataTransfer.clearData()
|
|
284
275
|
}
|
|
285
276
|
|
|
286
277
|
onDropFile = async (fromFiles, toFile, fromFileManager) => {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add button menu component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react'
|
|
6
|
+
import {
|
|
7
|
+
CodeFilled,
|
|
8
|
+
RightSquareFilled
|
|
9
|
+
} from '@ant-design/icons'
|
|
10
|
+
import BookmarksList from '../sidebar/bookmark-select'
|
|
11
|
+
import DragHandle from '../common/drag-handle'
|
|
12
|
+
|
|
13
|
+
const e = window.translate
|
|
14
|
+
|
|
15
|
+
export default function AddBtnMenu ({
|
|
16
|
+
menuRef,
|
|
17
|
+
menuPosition,
|
|
18
|
+
menuTop,
|
|
19
|
+
menuLeft,
|
|
20
|
+
onMenuScroll,
|
|
21
|
+
onTabAdd,
|
|
22
|
+
batch,
|
|
23
|
+
addPanelWidth,
|
|
24
|
+
setAddPanelWidth
|
|
25
|
+
}) {
|
|
26
|
+
const { onNewSsh } = window.store
|
|
27
|
+
const cls = 'pd2x pd1y context-item pointer'
|
|
28
|
+
const addTabBtn = window.store.hasNodePty
|
|
29
|
+
? (
|
|
30
|
+
<div
|
|
31
|
+
className={cls}
|
|
32
|
+
onClick={onTabAdd}
|
|
33
|
+
>
|
|
34
|
+
<RightSquareFilled /> {e('newTab')}
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
: null
|
|
38
|
+
|
|
39
|
+
const onDragEnd = useCallback((nw) => {
|
|
40
|
+
if (setAddPanelWidth) {
|
|
41
|
+
setAddPanelWidth(nw)
|
|
42
|
+
}
|
|
43
|
+
}, [setAddPanelWidth])
|
|
44
|
+
|
|
45
|
+
const onDragMove = useCallback((nw) => {
|
|
46
|
+
if (menuRef.current) {
|
|
47
|
+
menuRef.current.style.width = nw + 'px'
|
|
48
|
+
}
|
|
49
|
+
}, [menuRef])
|
|
50
|
+
|
|
51
|
+
const dragProps = {
|
|
52
|
+
min: 300,
|
|
53
|
+
max: 600,
|
|
54
|
+
width: addPanelWidth || 300,
|
|
55
|
+
onDragEnd,
|
|
56
|
+
onDragMove,
|
|
57
|
+
left: menuPosition === 'right'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log('render add btn menu', dragProps)
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
ref={menuRef}
|
|
65
|
+
className={`add-menu-wrap add-menu-${menuPosition}`}
|
|
66
|
+
style={{
|
|
67
|
+
maxHeight: window.innerHeight - menuTop - 50,
|
|
68
|
+
top: menuTop,
|
|
69
|
+
left: menuLeft,
|
|
70
|
+
width: addPanelWidth ? addPanelWidth + 'px' : undefined
|
|
71
|
+
}}
|
|
72
|
+
onScroll={onMenuScroll}
|
|
73
|
+
>
|
|
74
|
+
<DragHandle
|
|
75
|
+
{...dragProps}
|
|
76
|
+
/>
|
|
77
|
+
<div
|
|
78
|
+
className={cls}
|
|
79
|
+
onClick={onNewSsh}
|
|
80
|
+
>
|
|
81
|
+
<CodeFilled /> {e('newBookmark')}
|
|
82
|
+
</div>
|
|
83
|
+
{addTabBtn}
|
|
84
|
+
<BookmarksList
|
|
85
|
+
store={window.store}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -5,11 +5,9 @@
|
|
|
5
5
|
import React, { Component } from 'react'
|
|
6
6
|
import { createPortal } from 'react-dom'
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
PlusOutlined,
|
|
10
|
-
RightSquareFilled
|
|
8
|
+
PlusOutlined
|
|
11
9
|
} from '@ant-design/icons'
|
|
12
|
-
import
|
|
10
|
+
import AddBtnMenu from './add-btn-menu'
|
|
13
11
|
import classNames from 'classnames'
|
|
14
12
|
import hasActiveInput from '../../common/has-active-input'
|
|
15
13
|
import './add-btn.styl'
|
|
@@ -93,43 +91,23 @@ export default class AddBtn extends Component {
|
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
renderMenus = () => {
|
|
96
|
-
const { onNewSsh } = window.store
|
|
97
|
-
const cls = 'pd2x pd1y context-item pointer'
|
|
98
|
-
const addTabBtn = window.store.hasNodePty
|
|
99
|
-
? (
|
|
100
|
-
<div
|
|
101
|
-
className={cls}
|
|
102
|
-
onClick={this.handleTabAdd}
|
|
103
|
-
>
|
|
104
|
-
<RightSquareFilled /> {e('newTab')}
|
|
105
|
-
</div>
|
|
106
|
-
)
|
|
107
|
-
: null
|
|
108
|
-
|
|
109
94
|
const { menuPosition, menuTop, menuLeft } = this.state
|
|
95
|
+
const addBtnMenuProps = {
|
|
96
|
+
menuRef: this.menuRef,
|
|
97
|
+
menuPosition,
|
|
98
|
+
menuTop,
|
|
99
|
+
menuLeft,
|
|
100
|
+
onMenuScroll: this.handleMenuScroll,
|
|
101
|
+
onTabAdd: this.handleTabAdd,
|
|
102
|
+
batch: this.props.batch,
|
|
103
|
+
addPanelWidth: this.props.addPanelWidth,
|
|
104
|
+
setAddPanelWidth: window.store.setAddPanelWidth
|
|
105
|
+
}
|
|
110
106
|
|
|
111
107
|
return (
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
style={{
|
|
116
|
-
maxHeight: window.innerHeight - menuTop - 50,
|
|
117
|
-
top: menuTop,
|
|
118
|
-
left: menuLeft
|
|
119
|
-
}}
|
|
120
|
-
onScroll={this.handleMenuScroll}
|
|
121
|
-
>
|
|
122
|
-
<div
|
|
123
|
-
className={cls}
|
|
124
|
-
onClick={onNewSsh}
|
|
125
|
-
>
|
|
126
|
-
<CodeFilled /> {e('newBookmark')}
|
|
127
|
-
</div>
|
|
128
|
-
{addTabBtn}
|
|
129
|
-
<BookmarksList
|
|
130
|
-
store={window.store}
|
|
131
|
-
/>
|
|
132
|
-
</div>
|
|
108
|
+
<AddBtnMenu
|
|
109
|
+
{...addBtnMenuProps}
|
|
110
|
+
/>
|
|
133
111
|
)
|
|
134
112
|
}
|
|
135
113
|
|
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
border 1px solid var(--main-lighter)
|
|
7
7
|
border-radius 6px
|
|
8
8
|
box-shadow 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 1px var(--main-darker)
|
|
9
|
-
min-width
|
|
10
|
-
max-width
|
|
9
|
+
min-width 300px
|
|
10
|
+
max-width 600px
|
|
11
11
|
overflow-y auto
|
|
12
12
|
margin-top 4px
|
|
13
13
|
padding 4px 15px
|
|
14
|
+
.drag-handle
|
|
15
|
+
right 3px
|
|
16
|
+
display block
|
|
17
|
+
|
|
@@ -79,9 +79,11 @@ export default class VncSession extends RdpSession {
|
|
|
79
79
|
term: terminalType,
|
|
80
80
|
viewOnly = false,
|
|
81
81
|
scaleViewport = true,
|
|
82
|
+
clipViewport = false,
|
|
82
83
|
username,
|
|
83
84
|
password
|
|
84
85
|
} = tab
|
|
86
|
+
console.log('vnc tab', tab)
|
|
85
87
|
const opts = clone({
|
|
86
88
|
term: terminalType || config.terminalType,
|
|
87
89
|
tabId: id,
|
|
@@ -113,6 +115,7 @@ export default class VncSession extends RdpSession {
|
|
|
113
115
|
const { width, height } = this.state
|
|
114
116
|
const wsUrl = `${pre}://${hs}/vnc/${pid}?token=${tokenElecterm}&width=${width}&height=${height}`
|
|
115
117
|
const vncOpts = {
|
|
118
|
+
clipViewport,
|
|
116
119
|
scaleViewport,
|
|
117
120
|
viewOnly,
|
|
118
121
|
style: {
|
package/client/store/common.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
modals,
|
|
10
10
|
leftSidebarWidthKey,
|
|
11
11
|
rightSidebarWidthKey,
|
|
12
|
+
addPanelWidthLsKey,
|
|
12
13
|
dismissDelKeyTipLsKey,
|
|
13
14
|
connectionMap,
|
|
14
15
|
settingMap,
|
|
@@ -133,6 +134,11 @@ export default Store => {
|
|
|
133
134
|
window.store.leftSidebarWidth = v
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
Store.prototype.setAddPanelWidth = function (v) {
|
|
138
|
+
ls.setItem(addPanelWidthLsKey, v)
|
|
139
|
+
window.store.addPanelWidth = v
|
|
140
|
+
}
|
|
141
|
+
|
|
136
142
|
Store.prototype.setRightSidePanelWidth = function (v) {
|
|
137
143
|
ls.setItem(rightSidebarWidthKey, v)
|
|
138
144
|
window.store.rightPanelWidth = v
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
localAddrBookmarkLsKey,
|
|
17
17
|
leftSidebarWidthKey,
|
|
18
18
|
rightSidebarWidthKey,
|
|
19
|
+
addPanelWidthLsKey,
|
|
19
20
|
dismissDelKeyTipLsKey,
|
|
20
21
|
qmSortByFrequencyKey,
|
|
21
22
|
resolutionsLsKey,
|
|
@@ -151,6 +152,7 @@ export default () => {
|
|
|
151
152
|
// sidebar
|
|
152
153
|
openedSideBar: ls.getItem(openedSidebarKey),
|
|
153
154
|
leftSidebarWidth: parseInt(ls.getItem(leftSidebarWidthKey), 10) || 300,
|
|
155
|
+
addPanelWidth: parseInt(ls.getItem(addPanelWidthLsKey), 10) || 300,
|
|
154
156
|
menuOpened: false,
|
|
155
157
|
pinned: ls.getItem(sidebarPinnedKey) === 'true',
|
|
156
158
|
|
|
@@ -45,7 +45,9 @@ export async function addTabFromCommandLine (store, opts) {
|
|
|
45
45
|
options,
|
|
46
46
|
argv
|
|
47
47
|
} = opts
|
|
48
|
-
|
|
48
|
+
if (helpInfo) {
|
|
49
|
+
store.commandLineHelp = helpInfo
|
|
50
|
+
}
|
|
49
51
|
if (isHelp) {
|
|
50
52
|
return store.openAbout(infoTabs.cmd)
|
|
51
53
|
}
|
|
@@ -205,4 +207,10 @@ export default (Store) => {
|
|
|
205
207
|
Store.prototype.addTabFromCommandLine = (event, opts) => {
|
|
206
208
|
addTabFromCommandLine(window.store, opts)
|
|
207
209
|
}
|
|
210
|
+
Store.prototype.checkPendingDeepLink = async function () {
|
|
211
|
+
const pending = await window.pre.runGlobalAsync('getPendingDeepLink')
|
|
212
|
+
if (pending) {
|
|
213
|
+
addTabFromCommandLine(window.store, pending)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
208
216
|
}
|