@electerm/electerm-react 2.13.6 → 2.16.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.
- package/client/components/ai/ai-chat.jsx +44 -2
- package/client/components/ai/ai-stop-icon.jsx +13 -0
- package/client/components/ai/ai.styl +10 -0
- package/client/components/bg/css-overwrite.jsx +158 -187
- package/client/components/bg/custom-css.jsx +9 -15
- package/client/components/bookmark-form/bookmark-schema.js +7 -1
- package/client/components/bookmark-form/common/color-picker.jsx +4 -8
- package/client/components/bookmark-form/common/exec-settings-field.jsx +44 -0
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/config/common-fields.js +1 -0
- package/client/components/bookmark-form/config/local.js +3 -1
- package/client/components/common/animate-text.jsx +22 -23
- package/client/components/common/modal.jsx +2 -0
- package/client/components/common/notification.jsx +1 -1
- package/client/components/common/opacity.jsx +8 -6
- package/client/components/common/password.jsx +19 -32
- package/client/components/footer/cmd-history.jsx +154 -0
- package/client/components/footer/cmd-history.styl +73 -0
- package/client/components/footer/footer-entry.jsx +15 -1
- package/client/components/main/main.jsx +2 -3
- package/client/components/main/ui-theme.jsx +10 -6
- package/client/components/profile/profile-list.jsx +1 -1
- package/client/components/quick-commands/quick-commands-list.jsx +1 -1
- package/client/components/quick-commands/quick-commands-select.jsx +1 -4
- package/client/components/rdp/rdp-session.jsx +23 -4
- package/client/components/session/session.styl +1 -3
- package/client/components/setting-panel/list.styl +7 -0
- package/client/components/setting-panel/terminal-bg-config.jsx +2 -0
- package/client/components/setting-panel/text-bg-modal.jsx +9 -9
- package/client/components/setting-sync/setting-sync-form.jsx +10 -5
- package/client/components/sftp/file-item.jsx +22 -0
- package/client/components/sidebar/history-item.jsx +6 -3
- package/client/components/sidebar/history.jsx +48 -5
- package/client/components/sidebar/sidebar-panel.jsx +0 -13
- package/client/components/sidebar/sidebar.styl +19 -0
- package/client/components/ssh-config/load-ssh-configs-item.jsx +99 -0
- package/client/components/ssh-config/load-ssh-configs.jsx +38 -9
- package/client/components/ssh-config/ssh-config.styl +3 -0
- package/client/components/tabs/add-btn-menu.jsx +28 -4
- package/client/components/tabs/add-btn.jsx +1 -1
- package/client/components/tabs/add-btn.styl +8 -0
- package/client/components/terminal/terminal.jsx +28 -11
- package/client/components/terminal/transfer-client-base.js +44 -0
- package/client/components/terminal/trzsz-client.js +10 -11
- package/client/components/terminal/zmodem-client.js +10 -11
- package/client/components/text-editor/edit-with-custom-editor.jsx +49 -0
- package/client/components/text-editor/simple-editor.jsx +38 -6
- package/client/components/text-editor/text-editor-form.jsx +13 -5
- package/client/components/text-editor/text-editor.jsx +20 -1
- package/client/components/vnc/vnc-session.jsx +3 -0
- package/client/components/vnc/vnc.styl +1 -1
- package/client/store/bookmark.js +3 -11
- package/client/store/common.js +31 -4
- package/client/store/init-state.js +26 -1
- package/client/store/store.js +1 -1
- package/client/store/sync.js +2 -3
- package/client/store/watch.js +8 -1
- package/package.json +1 -1
- package/client/components/ssh-config/ssh-config-item.jsx +0 -24
|
@@ -3,15 +3,13 @@ import {
|
|
|
3
3
|
Input,
|
|
4
4
|
InputNumber,
|
|
5
5
|
Space,
|
|
6
|
-
Typography,
|
|
7
6
|
Select,
|
|
8
|
-
Button
|
|
7
|
+
Button,
|
|
8
|
+
Modal
|
|
9
9
|
} from 'antd'
|
|
10
|
-
import Modal from '../common/modal'
|
|
11
10
|
import { ColorPicker } from '../bookmark-form/common/color-picker.jsx'
|
|
12
11
|
|
|
13
12
|
const { TextArea } = Input
|
|
14
|
-
const { Title } = Typography
|
|
15
13
|
const e = window.translate
|
|
16
14
|
|
|
17
15
|
export default function TextBgModal ({
|
|
@@ -68,9 +66,9 @@ export default function TextBgModal ({
|
|
|
68
66
|
footer={footer}
|
|
69
67
|
>
|
|
70
68
|
<div className='pd1'>
|
|
71
|
-
<Space
|
|
69
|
+
<Space orientation='vertical' size='large' className='width-100'>
|
|
72
70
|
<div>
|
|
73
|
-
<
|
|
71
|
+
<b>{e('text')}</b>
|
|
74
72
|
<TextArea
|
|
75
73
|
value={text}
|
|
76
74
|
onChange={(e) => setText(e.target.value)}
|
|
@@ -81,7 +79,7 @@ export default function TextBgModal ({
|
|
|
81
79
|
</div>
|
|
82
80
|
|
|
83
81
|
<div>
|
|
84
|
-
<
|
|
82
|
+
<b>{e('fontSize')}</b>
|
|
85
83
|
<InputNumber
|
|
86
84
|
value={fontSize}
|
|
87
85
|
onChange={setFontSize}
|
|
@@ -93,7 +91,7 @@ export default function TextBgModal ({
|
|
|
93
91
|
</div>
|
|
94
92
|
|
|
95
93
|
<div>
|
|
96
|
-
<
|
|
94
|
+
<b>{e('textColor')}</b>
|
|
97
95
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
98
96
|
<ColorPicker
|
|
99
97
|
value={color}
|
|
@@ -109,7 +107,7 @@ export default function TextBgModal ({
|
|
|
109
107
|
</div>
|
|
110
108
|
|
|
111
109
|
<div>
|
|
112
|
-
<
|
|
110
|
+
<b>{e('fontFamily')}</b>
|
|
113
111
|
<Select
|
|
114
112
|
value={fontFamily}
|
|
115
113
|
onChange={setFontFamily}
|
|
@@ -140,3 +138,5 @@ export default function TextBgModal ({
|
|
|
140
138
|
</Modal>
|
|
141
139
|
)
|
|
142
140
|
}
|
|
141
|
+
|
|
142
|
+
TextBgModal.displayName = 'TextBgModal'
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* bookmark form
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { useEffect, useRef } from 'react'
|
|
9
9
|
import { ArrowDownOutlined, ArrowUpOutlined, SaveOutlined, ClearOutlined } from '@ant-design/icons'
|
|
10
10
|
import { Button, Input, Form, Alert } from 'antd'
|
|
11
11
|
import { notification } from '../common/notification'
|
|
@@ -27,10 +27,15 @@ function trim (str) {
|
|
|
27
27
|
|
|
28
28
|
export default function SyncForm (props) {
|
|
29
29
|
const [form] = Form.useForm()
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const prevRef = useRef(null)
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (prevRef.current && !eq(prevRef.current, props.formData)) {
|
|
34
|
+
form.resetFields()
|
|
35
|
+
}
|
|
36
|
+
prevRef.current = props.formData
|
|
37
|
+
}, [props.formData])
|
|
38
|
+
|
|
34
39
|
const { syncType } = props
|
|
35
40
|
function disabled () {
|
|
36
41
|
if (syncType === syncTypes.cloud) {
|
|
@@ -651,6 +651,28 @@ export default class FileSection extends React.Component {
|
|
|
651
651
|
this.watchFile(tempPath)
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
+
editWithCustomEditor = async (text, editorCommand) => {
|
|
655
|
+
const {
|
|
656
|
+
path,
|
|
657
|
+
name,
|
|
658
|
+
type
|
|
659
|
+
} = this.state.file
|
|
660
|
+
let tempPath = ''
|
|
661
|
+
if (type === typeMap.local) {
|
|
662
|
+
tempPath = window.pre.resolve(path, name)
|
|
663
|
+
} else {
|
|
664
|
+
const id = generate()
|
|
665
|
+
tempPath = window.pre.resolve(
|
|
666
|
+
window.pre.tempDir, `electerm-temp-${id}-${name}`
|
|
667
|
+
)
|
|
668
|
+
await fs.writeFile(tempPath, text)
|
|
669
|
+
}
|
|
670
|
+
this.watchingFile = tempPath
|
|
671
|
+
window.pre.runGlobalAsync('watchFile', tempPath)
|
|
672
|
+
await window.pre.runGlobalAsync('openFileWithEditor', tempPath, editorCommand)
|
|
673
|
+
window.pre.ipcOnEvent('file-change', this.onFileChange)
|
|
674
|
+
}
|
|
675
|
+
|
|
654
676
|
onFileChange = (e, text) => {
|
|
655
677
|
this.editor.editWithSystemEditorDone({
|
|
656
678
|
id: this.id,
|
|
@@ -6,8 +6,7 @@ import { refsStatic } from '../common/ref'
|
|
|
6
6
|
export default function HistoryItem (props) {
|
|
7
7
|
const { store } = window
|
|
8
8
|
const {
|
|
9
|
-
item
|
|
10
|
-
index
|
|
9
|
+
item
|
|
11
10
|
} = props
|
|
12
11
|
const timeoutRef = useRef(null)
|
|
13
12
|
|
|
@@ -30,7 +29,11 @@ export default function HistoryItem (props) {
|
|
|
30
29
|
|
|
31
30
|
function handleDelete (e) {
|
|
32
31
|
e.stopPropagation()
|
|
33
|
-
|
|
32
|
+
const { id } = item
|
|
33
|
+
const i = store.history.findIndex((i) => i.id === id)
|
|
34
|
+
if (i !== -1) {
|
|
35
|
+
store.history.splice(i, 1)
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
function handleBookmark (e) {
|
|
@@ -2,31 +2,74 @@
|
|
|
2
2
|
* history select
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import React, { useState, useEffect } from 'react'
|
|
5
6
|
import { auto } from 'manate/react'
|
|
7
|
+
import { Switch } from 'antd'
|
|
8
|
+
import { UnorderedListOutlined } from '@ant-design/icons'
|
|
6
9
|
import HistoryItem from './history-item'
|
|
10
|
+
import { getItemJSON, setItemJSON } from '../../common/safe-local-storage.js'
|
|
11
|
+
|
|
12
|
+
const SORT_BY_FREQ_KEY = 'electerm-history-sort-by-frequency'
|
|
7
13
|
|
|
8
14
|
export default auto(function HistoryPanel (props) {
|
|
9
15
|
const { store } = window
|
|
10
16
|
if (store.config.disableConnectionHistory) {
|
|
11
17
|
return null
|
|
12
18
|
}
|
|
19
|
+
const [sortByFrequency, setSortByFrequency] = useState(() => {
|
|
20
|
+
return getItemJSON(SORT_BY_FREQ_KEY, false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setItemJSON(SORT_BY_FREQ_KEY, sortByFrequency)
|
|
25
|
+
}, [sortByFrequency])
|
|
26
|
+
|
|
13
27
|
const {
|
|
14
28
|
history
|
|
15
29
|
} = store
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
let arr = [...history]
|
|
31
|
+
if (sortByFrequency) {
|
|
32
|
+
arr = arr.sort((a, b) => { return b.count - a.count })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleSortByFrequencyChange = (checked) => {
|
|
36
|
+
setSortByFrequency(checked)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const handleClearHistory = () => {
|
|
40
|
+
store.clearHistory()
|
|
41
|
+
}
|
|
42
|
+
const e = window.translate
|
|
43
|
+
const switchProps = {
|
|
44
|
+
checkedChildren: e('sortByFrequency'),
|
|
45
|
+
unCheckedChildren: e('sortByFrequency'),
|
|
46
|
+
checked: sortByFrequency,
|
|
47
|
+
onChange: handleSortByFrequencyChange,
|
|
48
|
+
size: 'small'
|
|
49
|
+
}
|
|
50
|
+
const clearIconProps = {
|
|
51
|
+
className: 'history-clear-icon pointer clear-ai-icon icon-hover',
|
|
52
|
+
title: window.translate('clear'),
|
|
53
|
+
onClick: handleClearHistory
|
|
54
|
+
}
|
|
19
55
|
return (
|
|
20
56
|
<div
|
|
21
57
|
className='sidebar-panel-history'
|
|
22
58
|
>
|
|
23
|
-
<div className='pd2x'>
|
|
59
|
+
<div className='history-header pd2x pd2b'>
|
|
60
|
+
<Switch
|
|
61
|
+
{...switchProps}
|
|
62
|
+
/>
|
|
63
|
+
<UnorderedListOutlined
|
|
64
|
+
{...clearIconProps}
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
<div className='history-body'>
|
|
24
68
|
{
|
|
25
69
|
arr.map((item, i) => {
|
|
26
70
|
return (
|
|
27
71
|
<HistoryItem
|
|
28
72
|
key={item.id}
|
|
29
|
-
index={i}
|
|
30
73
|
item={item}
|
|
31
74
|
/>
|
|
32
75
|
)
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
PlusCircleOutlined,
|
|
14
14
|
ShrinkOutlined,
|
|
15
15
|
PushpinOutlined,
|
|
16
|
-
UnorderedListOutlined,
|
|
17
16
|
SelectOutlined
|
|
18
17
|
} from '@ant-design/icons'
|
|
19
18
|
|
|
@@ -29,21 +28,9 @@ export default memo(function SidebarPanel (props) {
|
|
|
29
28
|
const prps1 = {
|
|
30
29
|
className: prps.className + (pinned ? ' pinned' : '')
|
|
31
30
|
}
|
|
32
|
-
const props2 = {
|
|
33
|
-
onClick: store.clearHistory,
|
|
34
|
-
className: 'mg2x pointer clear-ai-icon icon-hover'
|
|
35
|
-
}
|
|
36
|
-
const tabBarExtraContent = sidebarPanelTab === 'history'
|
|
37
|
-
? (
|
|
38
|
-
<UnorderedListOutlined
|
|
39
|
-
{...props2}
|
|
40
|
-
/>
|
|
41
|
-
)
|
|
42
|
-
: null
|
|
43
31
|
const tabsProps = {
|
|
44
32
|
activeKey: sidebarPanelTab,
|
|
45
33
|
onChange: store.handleSidebarPanelTab,
|
|
46
|
-
tabBarExtraContent,
|
|
47
34
|
items: [
|
|
48
35
|
{
|
|
49
36
|
key: 'bookmarks',
|
|
@@ -48,6 +48,25 @@
|
|
|
48
48
|
flex-direction column
|
|
49
49
|
overflow hidden
|
|
50
50
|
min-height 0
|
|
51
|
+
.history-header
|
|
52
|
+
flex-shrink 0
|
|
53
|
+
display flex
|
|
54
|
+
align-items center
|
|
55
|
+
border-bottom 1px solid var(--border)
|
|
56
|
+
position sticky
|
|
57
|
+
top 0
|
|
58
|
+
z-index 10
|
|
59
|
+
background var(--main)
|
|
60
|
+
.history-clear-icon
|
|
61
|
+
margin-left auto
|
|
62
|
+
margin-right 0
|
|
63
|
+
color var(--text-dark)
|
|
64
|
+
&:hover
|
|
65
|
+
color var(--error)
|
|
66
|
+
.history-body
|
|
67
|
+
flex 1
|
|
68
|
+
overflow-y auto
|
|
69
|
+
overflow-x hidden
|
|
51
70
|
.not-system-ui.is-mac
|
|
52
71
|
.sidebar-bar
|
|
53
72
|
margin-top 20px
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Input
|
|
4
|
+
} from 'antd'
|
|
5
|
+
import {
|
|
6
|
+
EditOutlined,
|
|
7
|
+
DeleteOutlined,
|
|
8
|
+
CheckOutlined,
|
|
9
|
+
CloseOutlined
|
|
10
|
+
} from '@ant-design/icons'
|
|
11
|
+
|
|
12
|
+
const { TextArea } = Input
|
|
13
|
+
|
|
14
|
+
export default function LoadSshConfigsItem (props) {
|
|
15
|
+
const { item, index, onDelete, onUpdate } = props
|
|
16
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
17
|
+
const [editValue, setEditValue] = useState(JSON.stringify(item, null, 2))
|
|
18
|
+
|
|
19
|
+
const handleToggleEdit = function () {
|
|
20
|
+
if (isEditing) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(editValue)
|
|
23
|
+
onUpdate(index, parsed)
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error('Invalid JSON:', err)
|
|
26
|
+
setEditValue(JSON.stringify(item, null, 2))
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
setEditValue(JSON.stringify(item, null, 2))
|
|
30
|
+
}
|
|
31
|
+
setIsEditing(!isEditing)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleDelete = function () {
|
|
35
|
+
onDelete(index)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleCancelEdit = function () {
|
|
39
|
+
setEditValue(JSON.stringify(item, null, 2))
|
|
40
|
+
setIsEditing(false)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderActions () {
|
|
44
|
+
if (isEditing) {
|
|
45
|
+
return [
|
|
46
|
+
<CheckOutlined
|
|
47
|
+
className='mg1r pointer icon-success'
|
|
48
|
+
onClick={handleToggleEdit}
|
|
49
|
+
key='confirm-ssh-config-item'
|
|
50
|
+
/>,
|
|
51
|
+
<CloseOutlined
|
|
52
|
+
className='mg1r pointer icon-warning'
|
|
53
|
+
onClick={handleCancelEdit}
|
|
54
|
+
key='cancel-ssh-config-item'
|
|
55
|
+
/>
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
return [
|
|
59
|
+
<EditOutlined
|
|
60
|
+
className='mg1r pointer ssh-config-item-edit-icon'
|
|
61
|
+
onClick={handleToggleEdit}
|
|
62
|
+
key='edit-ssh-config-item'
|
|
63
|
+
/>,
|
|
64
|
+
<DeleteOutlined
|
|
65
|
+
className='pointer icon-danger ssh-config-item-delete-icon'
|
|
66
|
+
onClick={handleDelete}
|
|
67
|
+
key='del-ssh-config-item'
|
|
68
|
+
/>
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderContent () {
|
|
73
|
+
if (isEditing) {
|
|
74
|
+
return (
|
|
75
|
+
<TextArea
|
|
76
|
+
value={editValue}
|
|
77
|
+
onChange={(e) => setEditValue(e.target.value)}
|
|
78
|
+
rows={10}
|
|
79
|
+
className='mg1t'
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
return (
|
|
84
|
+
<pre className='ssh-config-item-content'>
|
|
85
|
+
{JSON.stringify(item, null, 2)}
|
|
86
|
+
</pre>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className='ssh-config-item pd1'>
|
|
92
|
+
<div className='pd1b ssh-config-item-header'>
|
|
93
|
+
<b className='mg1r'>[{index + 1}]</b>
|
|
94
|
+
{renderActions()}
|
|
95
|
+
</div>
|
|
96
|
+
{renderContent()}
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -5,18 +5,20 @@ import {
|
|
|
5
5
|
Empty
|
|
6
6
|
} from 'antd'
|
|
7
7
|
import { useState, useEffect } from 'react'
|
|
8
|
-
import SshConfigItem from './ssh-config-item'
|
|
9
8
|
import * as ls from '../../common/safe-local-storage'
|
|
10
9
|
import {
|
|
11
10
|
sshConfigLoadKey
|
|
12
11
|
} from '../../common/constants'
|
|
13
12
|
import { ReloadOutlined } from '@ant-design/icons'
|
|
13
|
+
import LoadSshConfigsItem from './load-ssh-configs-item'
|
|
14
|
+
import './ssh-config.styl'
|
|
14
15
|
|
|
15
16
|
const e = window.translate
|
|
16
17
|
|
|
17
18
|
export default function LoadSshConfigs (props) {
|
|
18
19
|
const [loading, setLoading] = useState(false)
|
|
19
20
|
const { sshConfigs } = props
|
|
21
|
+
const [localConfigs, setLocalConfigs] = useState([])
|
|
20
22
|
|
|
21
23
|
const {
|
|
22
24
|
store
|
|
@@ -24,6 +26,11 @@ export default function LoadSshConfigs (props) {
|
|
|
24
26
|
const {
|
|
25
27
|
showSshConfigModal
|
|
26
28
|
} = props
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setLocalConfigs(sshConfigs)
|
|
32
|
+
}, [sshConfigs])
|
|
33
|
+
|
|
27
34
|
const handleCancel = function () {
|
|
28
35
|
store.showSshConfigModal = false
|
|
29
36
|
}
|
|
@@ -35,21 +42,43 @@ export default function LoadSshConfigs (props) {
|
|
|
35
42
|
|
|
36
43
|
const handleLoadSshConfig = function () {
|
|
37
44
|
store.showSshConfigModal = false
|
|
38
|
-
store.addSshConfigs(
|
|
45
|
+
store.addSshConfigs(localConfigs)
|
|
39
46
|
ls.setItem(sshConfigLoadKey, 'yes')
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
const handleDeleteItem = function (index) {
|
|
50
|
+
const newConfigs = [...localConfigs]
|
|
51
|
+
newConfigs.splice(index, 1)
|
|
52
|
+
setLocalConfigs(newConfigs)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleUpdateItem = function (index, newItem) {
|
|
56
|
+
const newConfigs = [...localConfigs]
|
|
57
|
+
newConfigs[index] = newItem
|
|
58
|
+
setLocalConfigs(newConfigs)
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
const renderList = function () {
|
|
43
|
-
if (!
|
|
62
|
+
if (!localConfigs.length) {
|
|
44
63
|
return (
|
|
45
64
|
<Empty />
|
|
46
65
|
)
|
|
47
66
|
}
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
return (
|
|
68
|
+
<div className='pd1b ssh-config-list'>
|
|
69
|
+
{
|
|
70
|
+
localConfigs.map((item, index) => (
|
|
71
|
+
<LoadSshConfigsItem
|
|
72
|
+
key={index}
|
|
73
|
+
item={item}
|
|
74
|
+
index={index}
|
|
75
|
+
onDelete={handleDeleteItem}
|
|
76
|
+
onUpdate={handleUpdateItem}
|
|
77
|
+
/>
|
|
78
|
+
))
|
|
79
|
+
}
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
53
82
|
}
|
|
54
83
|
|
|
55
84
|
useEffect(() => {
|
|
@@ -89,7 +118,7 @@ export default function LoadSshConfigs (props) {
|
|
|
89
118
|
type='primary'
|
|
90
119
|
className='mg1r mg1b'
|
|
91
120
|
onClick={handleLoadSshConfig}
|
|
92
|
-
disabled={!
|
|
121
|
+
disabled={!localConfigs.length || loading}
|
|
93
122
|
>
|
|
94
123
|
{e('import')}
|
|
95
124
|
</Button>
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* Add button menu component
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import React, { useCallback } from 'react'
|
|
5
|
+
import React, { useCallback, useState } from 'react'
|
|
6
|
+
import { Tabs } from 'antd'
|
|
6
7
|
import {
|
|
7
8
|
CodeFilled,
|
|
8
9
|
RightSquareFilled,
|
|
9
10
|
RobotOutlined
|
|
10
11
|
} from '@ant-design/icons'
|
|
11
12
|
import BookmarksList from '../sidebar/bookmark-select'
|
|
13
|
+
import History from '../sidebar/history'
|
|
12
14
|
import DragHandle from '../common/drag-handle'
|
|
13
15
|
import QuickConnect from './quick-connect'
|
|
14
16
|
|
|
@@ -26,6 +28,7 @@ export default function AddBtnMenu ({
|
|
|
26
28
|
setAddPanelWidth
|
|
27
29
|
}) {
|
|
28
30
|
const { onNewSsh, onNewSshAI } = window.store
|
|
31
|
+
const [activeTab, setActiveTab] = useState('bookmarks')
|
|
29
32
|
const cls = 'pd2x pd1y context-item pointer'
|
|
30
33
|
const addTabBtn = window.store.hasNodePty
|
|
31
34
|
? (
|
|
@@ -59,6 +62,24 @@ export default function AddBtnMenu ({
|
|
|
59
62
|
left: menuPosition === 'right'
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
const tabItems = [
|
|
66
|
+
{
|
|
67
|
+
key: 'bookmarks',
|
|
68
|
+
label: e('bookmarks')
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: 'history',
|
|
72
|
+
label: e('history')
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
let listContent
|
|
77
|
+
if (activeTab === 'bookmarks') {
|
|
78
|
+
listContent = <BookmarksList store={window.store} />
|
|
79
|
+
} else {
|
|
80
|
+
listContent = <History store={window.store} />
|
|
81
|
+
}
|
|
82
|
+
|
|
62
83
|
return (
|
|
63
84
|
<div
|
|
64
85
|
ref={menuRef}
|
|
@@ -89,11 +110,14 @@ export default function AddBtnMenu ({
|
|
|
89
110
|
<RobotOutlined /> {e('createBookmarkByAI')}
|
|
90
111
|
</div>
|
|
91
112
|
<QuickConnect batch={batch} inputOnly />
|
|
113
|
+
<Tabs
|
|
114
|
+
activeKey={activeTab}
|
|
115
|
+
onChange={setActiveTab}
|
|
116
|
+
items={tabItems}
|
|
117
|
+
/>
|
|
92
118
|
</div>
|
|
93
119
|
<div className='add-menu-list'>
|
|
94
|
-
|
|
95
|
-
store={window.store}
|
|
96
|
-
/>
|
|
120
|
+
{listContent}
|
|
97
121
|
</div>
|
|
98
122
|
</div>
|
|
99
123
|
)
|
|
@@ -118,7 +118,7 @@ export default class AddBtn extends Component {
|
|
|
118
118
|
focusSearchInput = () => {
|
|
119
119
|
// Focus the search input after the menu renders
|
|
120
120
|
this.focusTimeout = setTimeout(() => {
|
|
121
|
-
const searchInput = this.menuRef.current?.querySelector('.ant-input')
|
|
121
|
+
const searchInput = this.menuRef.current?.querySelector('.add-menu-list .ant-input')
|
|
122
122
|
if (searchInput) {
|
|
123
123
|
searchInput.focus()
|
|
124
124
|
searchInput.select()
|
|
@@ -82,7 +82,6 @@ class Term extends Component {
|
|
|
82
82
|
this.currentInput = ''
|
|
83
83
|
this.shellInjected = false
|
|
84
84
|
this.shellType = null
|
|
85
|
-
this.manualCommandHistory = new Set()
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
domRef = createRef()
|
|
@@ -737,7 +736,6 @@ class Term extends Component {
|
|
|
737
736
|
if (d === '\r' || d === '\n') {
|
|
738
737
|
const currentCmd = this.getCurrentInput()
|
|
739
738
|
if (currentCmd && currentCmd.trim() && this.shouldUseManualHistory()) {
|
|
740
|
-
this.manualCommandHistory.add(currentCmd.trim())
|
|
741
739
|
window.store.addCmdHistory(currentCmd.trim())
|
|
742
740
|
}
|
|
743
741
|
this.closeSuggestions()
|
|
@@ -903,9 +901,7 @@ class Term extends Component {
|
|
|
903
901
|
}
|
|
904
902
|
|
|
905
903
|
shouldUseManualHistory = () => {
|
|
906
|
-
|
|
907
|
-
(this.shellType === 'sh' || (isWin && this.isLocal()))
|
|
908
|
-
return useManual
|
|
904
|
+
return !this.cmdAddon || !this.cmdAddon.hasShellIntegration()
|
|
909
905
|
}
|
|
910
906
|
|
|
911
907
|
canInjectShellIntegration = () => {
|
|
@@ -1078,6 +1074,32 @@ class Term extends Component {
|
|
|
1078
1074
|
const { savePassword } = this.state
|
|
1079
1075
|
const termType = type
|
|
1080
1076
|
const extra = this.props.sessionOptions
|
|
1077
|
+
// Determine if this is a local terminal (no host)
|
|
1078
|
+
const isLocalType = !tab.host
|
|
1079
|
+
// Build exec settings: only for local type, prefer tab settings over config
|
|
1080
|
+
let execOpts = {}
|
|
1081
|
+
let execPropName = 'execLinux'
|
|
1082
|
+
if (isWin) {
|
|
1083
|
+
execPropName = 'execWindows'
|
|
1084
|
+
} else if (isMac) {
|
|
1085
|
+
execPropName = 'execMac'
|
|
1086
|
+
}
|
|
1087
|
+
if (isLocalType) {
|
|
1088
|
+
// Check flat properties on tab first (bookmark data), then fall back to config
|
|
1089
|
+
if (tab[execPropName]) {
|
|
1090
|
+
// Use bookmark's exec setting directly
|
|
1091
|
+
execOpts = {
|
|
1092
|
+
[execPropName]: tab[execPropName],
|
|
1093
|
+
[`${execPropName}Args`]: tab[`${execPropName}Args`] || []
|
|
1094
|
+
}
|
|
1095
|
+
} else if (config[execPropName]) {
|
|
1096
|
+
// Use global config exec settings
|
|
1097
|
+
execOpts = {
|
|
1098
|
+
[execPropName]: config[execPropName],
|
|
1099
|
+
[`${execPropName}Args`]: config[`${execPropName}Args`] || []
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1081
1103
|
const opts = clone({
|
|
1082
1104
|
cols,
|
|
1083
1105
|
rows,
|
|
@@ -1085,18 +1107,13 @@ class Term extends Component {
|
|
|
1085
1107
|
saveTerminalLogToFile: config.saveTerminalLogToFile,
|
|
1086
1108
|
...tab,
|
|
1087
1109
|
...extra,
|
|
1110
|
+
...execOpts,
|
|
1088
1111
|
logName,
|
|
1089
1112
|
sessionLogPath: config.sessionLogPath || createDefaultLogPath(),
|
|
1090
1113
|
...pick(config, [
|
|
1091
1114
|
'addTimeStampToTermLog',
|
|
1092
1115
|
'keepaliveInterval',
|
|
1093
1116
|
'keepaliveCountMax',
|
|
1094
|
-
'execWindows',
|
|
1095
|
-
'execMac',
|
|
1096
|
-
'execLinux',
|
|
1097
|
-
'execWindowsArgs',
|
|
1098
|
-
'execMacArgs',
|
|
1099
|
-
'execLinuxArgs',
|
|
1100
1117
|
'keyword2FA',
|
|
1101
1118
|
'debug'
|
|
1102
1119
|
]),
|