@electerm/electerm-react 3.6.16 → 3.7.9
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/parse-quick-connect.js +6 -1
- package/client/components/bookmark-form/ai-bookmark-form.jsx +20 -8
- package/client/components/bookmark-form/common/proxy.jsx +9 -15
- package/client/components/bookmark-form/fix-bookmark-default.js +4 -2
- package/client/components/bookmark-form/tree-select.jsx +3 -1
- package/client/components/file-transfer/transfer.jsx +11 -9
- package/client/components/file-transfer/transports-action-store.jsx +1 -14
- package/client/components/main/main.jsx +0 -2
- package/client/components/sftp/list-table-ui.jsx +2 -2
- package/client/components/sftp/paged-list.jsx +1 -1
- package/package.json +1 -1
- package/client/components/common/auto-check-update.jsx +0 -31
|
@@ -370,7 +370,12 @@ function parseQuickConnect (str) {
|
|
|
370
370
|
opts.port = parseInt(port, 10)
|
|
371
371
|
}
|
|
372
372
|
if (username !== undefined && username !== '') {
|
|
373
|
-
|
|
373
|
+
// FTP form uses 'user' instead of 'username'
|
|
374
|
+
if (finalProtocol === 'ftp') {
|
|
375
|
+
opts.user = username
|
|
376
|
+
} else {
|
|
377
|
+
opts.username = username
|
|
378
|
+
}
|
|
374
379
|
}
|
|
375
380
|
if (password !== undefined && password !== '') {
|
|
376
381
|
opts.password = password
|
|
@@ -80,8 +80,18 @@ export default function AIBookmarkForm (props) {
|
|
|
80
80
|
|
|
81
81
|
let bookmarkData
|
|
82
82
|
if (aiResponse && aiResponse.response) {
|
|
83
|
+
// Normalize response payload to string before JSON extraction.
|
|
84
|
+
const rawResponse = aiResponse.response
|
|
85
|
+
let jsonStr = ''
|
|
86
|
+
if (typeof rawResponse === 'string') {
|
|
87
|
+
jsonStr = rawResponse
|
|
88
|
+
} else if (typeof rawResponse === 'object') {
|
|
89
|
+
jsonStr = JSON.stringify(rawResponse)
|
|
90
|
+
} else {
|
|
91
|
+
jsonStr = String(rawResponse)
|
|
92
|
+
}
|
|
83
93
|
// Parse the JSON response
|
|
84
|
-
|
|
94
|
+
jsonStr = jsonStr.trim()
|
|
85
95
|
// Remove markdown code blocks if present
|
|
86
96
|
if (jsonStr.startsWith('```json')) {
|
|
87
97
|
jsonStr = jsonStr.slice(7)
|
|
@@ -93,7 +103,7 @@ export default function AIBookmarkForm (props) {
|
|
|
93
103
|
}
|
|
94
104
|
jsonStr = jsonStr.trim()
|
|
95
105
|
|
|
96
|
-
bookmarkData =
|
|
106
|
+
bookmarkData = getGeneratedData(jsonStr)
|
|
97
107
|
const pretty = JSON.stringify(bookmarkData, null, 2)
|
|
98
108
|
setEditorText(pretty)
|
|
99
109
|
// set default category when preview opens
|
|
@@ -103,22 +113,23 @@ export default function AIBookmarkForm (props) {
|
|
|
103
113
|
}
|
|
104
114
|
} catch (error) {
|
|
105
115
|
console.error('AI bookmark generation error:', error)
|
|
106
|
-
message.error(
|
|
116
|
+
message.error('Can not generate bookmarks from AI response: ' + error.message)
|
|
107
117
|
} finally {
|
|
108
118
|
setLoading(false)
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
function getGeneratedData () {
|
|
113
|
-
if (!
|
|
122
|
+
function getGeneratedData (txt = editorText) {
|
|
123
|
+
if (!txt) return []
|
|
114
124
|
let parsed = null
|
|
115
125
|
try {
|
|
116
|
-
parsed =
|
|
126
|
+
parsed = JSON.parse(txt)
|
|
117
127
|
} catch (err) {
|
|
118
128
|
return []
|
|
119
129
|
}
|
|
120
130
|
if (!parsed) return []
|
|
121
|
-
|
|
131
|
+
const arr = Array.isArray(parsed) ? parsed : [parsed]
|
|
132
|
+
return arr.map(d => fixBookmarkData(d))
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
const createBookmark = async (bm) => {
|
|
@@ -228,6 +239,7 @@ export default function AIBookmarkForm (props) {
|
|
|
228
239
|
}
|
|
229
240
|
return (
|
|
230
241
|
<SimpleEditor
|
|
242
|
+
key='editor'
|
|
231
243
|
{...editorProps}
|
|
232
244
|
/>
|
|
233
245
|
)
|
|
@@ -238,7 +250,7 @@ export default function AIBookmarkForm (props) {
|
|
|
238
250
|
return renderEditor()
|
|
239
251
|
}
|
|
240
252
|
return (
|
|
241
|
-
<pre className='ai-bookmark-json-preview'>
|
|
253
|
+
<pre key='preview' className='ai-bookmark-json-preview'>
|
|
242
254
|
{editorText}
|
|
243
255
|
</pre>
|
|
244
256
|
)
|
|
@@ -17,17 +17,13 @@ export default function renderProxy (props) {
|
|
|
17
17
|
}
|
|
18
18
|
return prev
|
|
19
19
|
}, {})
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}),
|
|
28
|
-
placeholder: 'socks5://127.0.0.1:1080',
|
|
29
|
-
allowClear: true
|
|
30
|
-
}
|
|
20
|
+
const options = Object.keys(proxyTree)
|
|
21
|
+
.map(d => {
|
|
22
|
+
return {
|
|
23
|
+
label: d,
|
|
24
|
+
value: d
|
|
25
|
+
}
|
|
26
|
+
})
|
|
31
27
|
return (
|
|
32
28
|
<FormItem
|
|
33
29
|
{...formItemLayout}
|
|
@@ -38,10 +34,8 @@ export default function renderProxy (props) {
|
|
|
38
34
|
max: 1024, message: '1024 chars max'
|
|
39
35
|
}]}
|
|
40
36
|
>
|
|
41
|
-
<AutoComplete
|
|
42
|
-
|
|
43
|
-
>
|
|
44
|
-
<Input />
|
|
37
|
+
<AutoComplete options={options}>
|
|
38
|
+
<Input allowClear placeholder='socks5://127.0.0.1:1080' />
|
|
45
39
|
</AutoComplete>
|
|
46
40
|
</FormItem>
|
|
47
41
|
)
|
|
@@ -9,8 +9,10 @@ const defaultValues = {
|
|
|
9
9
|
x11: false,
|
|
10
10
|
term: 'xterm-256color',
|
|
11
11
|
displayRaw: false,
|
|
12
|
+
authType: 'password',
|
|
12
13
|
encode: 'utf8',
|
|
13
|
-
envLang: 'en_US.UTF-8'
|
|
14
|
+
envLang: 'en_US.UTF-8',
|
|
15
|
+
username: 'root'
|
|
14
16
|
},
|
|
15
17
|
telnet: {
|
|
16
18
|
port: 23
|
|
@@ -44,7 +46,7 @@ const defaultValues = {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
const requiredFields = {
|
|
47
|
-
ssh: ['host'
|
|
49
|
+
ssh: ['host'],
|
|
48
50
|
telnet: ['host'],
|
|
49
51
|
serial: ['path'],
|
|
50
52
|
vnc: ['host'],
|
|
@@ -113,6 +113,7 @@ export default function BookmarkTreeSelect (props) {
|
|
|
113
113
|
const [expandedKeys, setExpandedKeys] = useState(() => deepCopy(propExpandedKeys || []))
|
|
114
114
|
const [checkedKeys, setCheckedKeys] = useState(() => deepCopy(propCheckedKeys || []))
|
|
115
115
|
const [searchText, setSearchText] = useState('')
|
|
116
|
+
const [refreshKey, setRefreshKey] = useState(0)
|
|
116
117
|
|
|
117
118
|
const onCheck = setCheckedKeys
|
|
118
119
|
|
|
@@ -122,6 +123,7 @@ export default function BookmarkTreeSelect (props) {
|
|
|
122
123
|
if (type === 'delete') {
|
|
123
124
|
store.delItems(arr, settingMap.bookmarks)
|
|
124
125
|
store.delItems(arr, settingMap.bookmarkGroups)
|
|
126
|
+
setRefreshKey(k => k + 1)
|
|
125
127
|
} else {
|
|
126
128
|
store.openBookmarks(arr)
|
|
127
129
|
if (props.onClose) {
|
|
@@ -141,7 +143,7 @@ export default function BookmarkTreeSelect (props) {
|
|
|
141
143
|
setCheckedKeys([])
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
const treeData = useMemo(() => buildData(bookmarks, bookmarkGroups, searchText), [bookmarks, bookmarkGroups, searchText])
|
|
146
|
+
const treeData = useMemo(() => buildData(bookmarks, bookmarkGroups, searchText), [bookmarks, bookmarkGroups, searchText, refreshKey])
|
|
145
147
|
|
|
146
148
|
// Auto expand parent nodes when searching
|
|
147
149
|
const handleExpand = (keys) => {
|
|
@@ -71,9 +71,6 @@ export default class TransportAction extends Component {
|
|
|
71
71
|
this.transport = null
|
|
72
72
|
this.fromFile = null
|
|
73
73
|
refsTransfers.remove(this.id)
|
|
74
|
-
if (this.isFtp) {
|
|
75
|
-
window.initingFtpTabIds?.delete(this.tabId)
|
|
76
|
-
}
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
localCheckExist = (path) => {
|
|
@@ -241,10 +238,11 @@ export default class TransportAction extends Component {
|
|
|
241
238
|
operation // 'mv' or 'cp'
|
|
242
239
|
} = transfer
|
|
243
240
|
|
|
244
|
-
|
|
241
|
+
// Use this.newPath when set (e.g. user chose rename from conflict modal)
|
|
242
|
+
let finalToPath = this.newPath || toPath
|
|
245
243
|
|
|
246
|
-
// Check if it's a copy operation to the same path
|
|
247
|
-
if (fromPath === toPath && operation === fileOperationsMap.cp) {
|
|
244
|
+
// Check if it's a copy operation to the same path (no rename decision pending)
|
|
245
|
+
if (!this.newPath && fromPath === toPath && operation === fileOperationsMap.cp) {
|
|
248
246
|
finalToPath = this.handleRename(toPath, typeFrom === typeMap.remote).newPath
|
|
249
247
|
transfer.toPath = finalToPath
|
|
250
248
|
this.update({
|
|
@@ -410,6 +408,10 @@ export default class TransportAction extends Component {
|
|
|
410
408
|
this.newName = newName
|
|
411
409
|
}
|
|
412
410
|
|
|
411
|
+
const { typeFrom, typeTo } = this.props.transfer
|
|
412
|
+
if (typeFrom === typeTo) {
|
|
413
|
+
return this.mvOrCp()
|
|
414
|
+
}
|
|
413
415
|
this.startTransfer()
|
|
414
416
|
}
|
|
415
417
|
|
|
@@ -758,9 +760,9 @@ export default class TransportAction extends Component {
|
|
|
758
760
|
|
|
759
761
|
const list = await this.list(typeFrom, fromPath, tabId)
|
|
760
762
|
const bigFileSize = 1024 * 1024
|
|
761
|
-
const smallFilesBatch =
|
|
762
|
-
const BigFilesBatch =
|
|
763
|
-
const foldersBatch =
|
|
763
|
+
const smallFilesBatch = 30
|
|
764
|
+
const BigFilesBatch = 3
|
|
765
|
+
const foldersBatch = 50
|
|
764
766
|
|
|
765
767
|
const {
|
|
766
768
|
folders,
|
|
@@ -9,8 +9,6 @@ import { maxTransport } from '../../common/constants'
|
|
|
9
9
|
import { refsStatic } from '../common/ref'
|
|
10
10
|
// import { action } from 'manate'
|
|
11
11
|
|
|
12
|
-
window.initingFtpTabIds = new Set()
|
|
13
|
-
|
|
14
12
|
export default class TransportsActionStore extends Component {
|
|
15
13
|
constructor (props) {
|
|
16
14
|
super(props)
|
|
@@ -84,9 +82,7 @@ export default class TransportsActionStore extends Component {
|
|
|
84
82
|
typeTo,
|
|
85
83
|
typeFrom,
|
|
86
84
|
inited,
|
|
87
|
-
id
|
|
88
|
-
tabType,
|
|
89
|
-
tabId
|
|
85
|
+
id
|
|
90
86
|
} = tr
|
|
91
87
|
|
|
92
88
|
const isTransfer = typeTo !== typeFrom
|
|
@@ -95,15 +91,6 @@ export default class TransportsActionStore extends Component {
|
|
|
95
91
|
continue
|
|
96
92
|
}
|
|
97
93
|
|
|
98
|
-
// For ftp transfers, ensure only one per tabId is inited
|
|
99
|
-
if (tabType === 'ftp') {
|
|
100
|
-
const hasInited = fileTransfers.some(t => t.tabId === tabId && t.inited && t.id !== id)
|
|
101
|
-
if (hasInited || window.initingFtpTabIds.has(tabId)) {
|
|
102
|
-
continue
|
|
103
|
-
}
|
|
104
|
-
window.initingFtpTabIds.add(tabId)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
94
|
if (count < maxTransport) {
|
|
108
95
|
count++
|
|
109
96
|
this.pendingInitIds.add(id)
|
|
@@ -34,7 +34,6 @@ import InputContextMenu from '../common/input-context-menu'
|
|
|
34
34
|
import WorkspaceSaveModal from '../tabs/workspace-save-modal'
|
|
35
35
|
import BookmarkFromHistoryModal from '../bookmark-form/bookmark-from-history-modal'
|
|
36
36
|
import AutoSync from '../setting-sync/auto-sync'
|
|
37
|
-
import AutoCheckUpdate from '../common/auto-check-update'
|
|
38
37
|
import BatchOpRunner from '../batch-op/batch-op-runner'
|
|
39
38
|
import { pick } from 'lodash-es'
|
|
40
39
|
import deepCopy from 'json-deep-copy'
|
|
@@ -292,7 +291,6 @@ export default auto(function Index (props) {
|
|
|
292
291
|
<TerminalCmdSuggestions {...cmdSuggestionsProps} />
|
|
293
292
|
<TransferQueue />
|
|
294
293
|
<AutoSync config={config} />
|
|
295
|
-
<AutoCheckUpdate config={config} />
|
|
296
294
|
<WorkspaceSaveModal store={store} />
|
|
297
295
|
<BookmarkFromHistoryModal />
|
|
298
296
|
<NotificationContainer />
|
|
@@ -214,10 +214,10 @@ export default class FileListTable extends Component {
|
|
|
214
214
|
this.currentFileId = id
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
renderItem = (item) => {
|
|
217
|
+
renderItem = (item, index) => {
|
|
218
218
|
const { type } = this.props
|
|
219
219
|
const cls = item.isParent ? 'parent-file-item' : 'real-file-item'
|
|
220
|
-
const key = item.id
|
|
220
|
+
const key = item.id ?? index + 'file-item'
|
|
221
221
|
const fileProps = {
|
|
222
222
|
...this.props.getFileProps(item, type),
|
|
223
223
|
cls,
|
package/package.json
CHANGED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
|
-
|
|
3
|
-
export default function AutoCheckUpdate ({ config }) {
|
|
4
|
-
const lastCheckTimeRef = useRef(0)
|
|
5
|
-
const intervalIdRef = useRef(null)
|
|
6
|
-
|
|
7
|
-
useEffect(() => {
|
|
8
|
-
if (!config.checkUpdateOnStart) {
|
|
9
|
-
clearInterval(intervalIdRef.current)
|
|
10
|
-
return
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const checkForUpdate = () => {
|
|
14
|
-
const { store } = window
|
|
15
|
-
if (store.config.checkUpdateOnStart) {
|
|
16
|
-
store.onCheckUpdate(false)
|
|
17
|
-
}
|
|
18
|
-
lastCheckTimeRef.current = Date.now()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
intervalIdRef.current = setInterval(checkForUpdate, 60 * 60 * 1000)
|
|
22
|
-
|
|
23
|
-
return () => {
|
|
24
|
-
if (intervalIdRef.current) {
|
|
25
|
-
clearInterval(intervalIdRef.current)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}, [config.checkUpdateOnStart])
|
|
29
|
-
|
|
30
|
-
return null
|
|
31
|
-
}
|