@electerm/electerm-react 1.70.6 → 1.72.16
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/cache.js +56 -0
- package/client/common/constants.js +2 -0
- package/client/common/default-setting.js +2 -1
- package/client/common/download.jsx +5 -7
- package/client/common/setting-list.js +27 -0
- package/client/components/ai/ai-cache.jsx +36 -0
- package/client/components/ai/ai-chat-history-item.jsx +1 -1
- package/client/components/ai/ai-chat.jsx +5 -40
- package/client/components/ai/ai-config-props.js +7 -0
- package/client/components/ai/ai-config.jsx +5 -14
- package/client/components/ai/ai.styl +0 -4
- package/client/components/ai/providers.js +2 -2
- package/client/components/batch-op/batch-op-entry.jsx +1 -1
- package/client/components/batch-op/batch-op.jsx +2 -2
- package/client/components/bookmark-form/form-ssh-common.jsx +2 -3
- package/client/components/bookmark-form/form-tabs.jsx +2 -2
- package/client/components/bookmark-form/render-connection-hopping.jsx +2 -2
- package/client/components/bookmark-form/render-delayed-scripts.jsx +4 -4
- package/client/components/bookmark-form/render-ssh-tunnel.jsx +2 -2
- package/client/components/bookmark-form/sftp-enable.jsx +9 -0
- package/client/components/bookmark-form/ssh-form-ui.jsx +1 -0
- package/client/components/bookmark-form/ssh-form.jsx +3 -0
- package/client/components/bookmark-form/use-quick-commands.jsx +2 -2
- package/client/components/bookmark-form/x11.jsx +78 -9
- package/client/components/common/input-auto-focus.jsx +3 -11
- package/client/components/layout/layout.jsx +2 -1
- package/client/components/main/main.jsx +5 -0
- package/client/components/profile/profile-form-elem.jsx +1 -3
- package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -3
- package/client/components/quick-commands/quick-commands-list-form.jsx +2 -2
- package/client/components/session/session.jsx +80 -19
- package/client/components/session/session.styl +10 -3
- package/client/components/session/sessions.jsx +2 -1
- package/client/components/setting-panel/keywords-form.jsx +36 -38
- package/client/components/setting-panel/setting-modal.jsx +2 -2
- package/client/components/setting-panel/setting-terminal.jsx +2 -1
- package/client/components/setting-panel/tab-settings.jsx +26 -0
- package/client/components/sftp/address-bar.jsx +9 -2
- package/client/components/sftp/address-bookmark.jsx +4 -6
- package/client/components/sftp/file-item.jsx +1 -1
- package/client/components/sftp/file-read.js +14 -19
- package/client/components/sftp/keyword-filter.jsx +63 -0
- package/client/components/sftp/list-table-ui.jsx +7 -9
- package/client/components/sftp/sftp-entry.jsx +46 -9
- package/client/components/sftp/sftp.styl +6 -1
- package/client/components/sftp/transfer-conflict-store.jsx +1 -1
- package/client/components/shortcuts/shortcut-control.jsx +20 -0
- package/client/components/shortcuts/shortcut-editor.jsx +2 -2
- package/client/components/shortcuts/shortcuts.jsx +2 -2
- package/client/components/sidebar/info-modal.jsx +2 -2
- package/client/components/sidebar/transfer-list-control.jsx +18 -20
- package/client/components/ssh-config/ssh-config-item.jsx +2 -4
- package/client/components/ssh-config/ssh-config-load-notify.jsx +2 -2
- package/client/components/sys-menu/zoom.jsx +2 -2
- package/client/components/tabs/index.jsx +1 -1
- package/client/components/tabs/tab.jsx +3 -3
- package/client/components/terminal/cmd-item.jsx +32 -0
- package/client/components/terminal/command-tracker-addon.js +3 -1
- package/client/components/terminal/term-search.jsx +5 -6
- package/client/components/terminal/terminal-command-dropdown.jsx +303 -0
- package/client/components/terminal/terminal.jsx +88 -8
- package/client/components/terminal/terminal.styl +58 -0
- package/client/components/terminal-info/terminal-info.jsx +2 -2
- package/client/components/tree-list/tree-list.jsx +1 -1
- package/client/components/web/address-bar.jsx +2 -2
- package/client/store/common.js +27 -2
- package/client/store/init-state.js +3 -3
- package/client/store/item.js +2 -1
- package/client/store/setting.js +3 -2
- package/client/store/store.js +23 -24
- package/client/store/watch.js +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { Component } from 'manate/react/class-components'
|
|
2
|
+
import { refsStatic, refs } from '../common/ref'
|
|
3
|
+
import SuggestionItem from './cmd-item'
|
|
4
|
+
import { aiSuggestionsCache } from '../../common/cache'
|
|
5
|
+
import uid from '../../common/uid'
|
|
6
|
+
import classnames from 'classnames'
|
|
7
|
+
import {
|
|
8
|
+
LoadingOutlined
|
|
9
|
+
} from '@ant-design/icons'
|
|
10
|
+
|
|
11
|
+
export default class TerminalCmdSuggestions extends Component {
|
|
12
|
+
state = {
|
|
13
|
+
cursorPosition: {},
|
|
14
|
+
showSuggestions: false,
|
|
15
|
+
loadingAiSuggestions: false,
|
|
16
|
+
aiSuggestions: [],
|
|
17
|
+
cmdIsDescription: false,
|
|
18
|
+
reverse: false,
|
|
19
|
+
cmd: ''
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
componentDidMount () {
|
|
23
|
+
refsStatic.add('terminal-suggestions', this)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
componentWillUnmount () {
|
|
27
|
+
refsStatic.remove('terminal-suggestions')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
parseAiSuggestions = (aiResponse) => {
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(aiResponse.response).map(d => {
|
|
33
|
+
return {
|
|
34
|
+
command: d,
|
|
35
|
+
type: 'AI'
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.log('parseAiSuggestions error:', e)
|
|
40
|
+
return []
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getAiSuggestions = async (event) => {
|
|
45
|
+
event.stopPropagation()
|
|
46
|
+
const { cmd } = this.state
|
|
47
|
+
if (window.store.aiConfigMissing()) {
|
|
48
|
+
window.store.toggleAIConfig()
|
|
49
|
+
}
|
|
50
|
+
this.setState({
|
|
51
|
+
loadingAiSuggestions: true
|
|
52
|
+
})
|
|
53
|
+
const {
|
|
54
|
+
config
|
|
55
|
+
} = window.store
|
|
56
|
+
const prompt = `give me max 5 command suggestions for user input: "${cmd}", return pure json format result only, no extra words, no markdown format, follow this format: ["command1","command2"...]`
|
|
57
|
+
const cached = aiSuggestionsCache.get(cmd)
|
|
58
|
+
if (cached) {
|
|
59
|
+
this.setState({
|
|
60
|
+
loadingAiSuggestions: false,
|
|
61
|
+
aiSuggestions: cached
|
|
62
|
+
})
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const aiResponse = aiSuggestionsCache.get(prompt) || await window.pre.runGlobalAsync(
|
|
67
|
+
'AIchat',
|
|
68
|
+
prompt,
|
|
69
|
+
config.modelAI,
|
|
70
|
+
config.roleAI,
|
|
71
|
+
config.baseURLAI,
|
|
72
|
+
config.apiPathAI,
|
|
73
|
+
config.apiKeyAI
|
|
74
|
+
).catch(
|
|
75
|
+
window.store.onError
|
|
76
|
+
)
|
|
77
|
+
if (cmd !== this.state.cmd) {
|
|
78
|
+
this.setState({
|
|
79
|
+
loadingAiSuggestions: false
|
|
80
|
+
})
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
if (aiResponse && aiResponse.error) {
|
|
84
|
+
this.setState({
|
|
85
|
+
loadingAiSuggestions: false
|
|
86
|
+
})
|
|
87
|
+
return window.store.onError(
|
|
88
|
+
new Error(aiResponse.error)
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
this.setState({
|
|
92
|
+
loadingAiSuggestions: false,
|
|
93
|
+
aiSuggestions: this.parseAiSuggestions(aiResponse, cmd)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
openSuggestions = (cursorPosition, cmd) => {
|
|
98
|
+
if (!this.state.showSuggestions) {
|
|
99
|
+
document.addEventListener('click', this.handleClickOutside)
|
|
100
|
+
document.addEventListener('keydown', this.handleKeyDown)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const {
|
|
104
|
+
left,
|
|
105
|
+
top,
|
|
106
|
+
cellHeight
|
|
107
|
+
} = cursorPosition
|
|
108
|
+
const w = window.innerWidth
|
|
109
|
+
const h = window.innerHeight
|
|
110
|
+
|
|
111
|
+
const position = {}
|
|
112
|
+
const reverse = top > h / 2
|
|
113
|
+
|
|
114
|
+
// Use right position if close to right edge
|
|
115
|
+
if (left > w / 2) {
|
|
116
|
+
position.right = w - left
|
|
117
|
+
} else {
|
|
118
|
+
position.left = left
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Use bottom position if close to bottom edge
|
|
122
|
+
if (reverse) {
|
|
123
|
+
position.bottom = h - top + cellHeight
|
|
124
|
+
} else {
|
|
125
|
+
position.top = top
|
|
126
|
+
}
|
|
127
|
+
this.setState({
|
|
128
|
+
showSuggestions: true,
|
|
129
|
+
cursorPosition: position,
|
|
130
|
+
cmd,
|
|
131
|
+
reverse
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
closeSuggestions = () => {
|
|
136
|
+
document.removeEventListener('click', this.handleClickOutside)
|
|
137
|
+
document.removeEventListener('keydown', this.handleKeyDown)
|
|
138
|
+
const {
|
|
139
|
+
aiSuggestions
|
|
140
|
+
} = this.state
|
|
141
|
+
if (aiSuggestions.length) {
|
|
142
|
+
aiSuggestionsCache.set(this.state.cmd, aiSuggestions)
|
|
143
|
+
aiSuggestions.forEach(item => {
|
|
144
|
+
window.store.addCmdHistory(item.command, 'aiCmdHistory')
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
this.setState({
|
|
148
|
+
showSuggestions: false,
|
|
149
|
+
aiSuggestions: []
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
handleClickOutside = (event) => {
|
|
154
|
+
const suggestionElement = document.querySelector('.terminal-suggestions-wrap')
|
|
155
|
+
if (suggestionElement && !suggestionElement.contains(event.target)) {
|
|
156
|
+
this.closeSuggestions()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
handleKeyDown = (event) => {
|
|
161
|
+
if (event.key === 'Escape') {
|
|
162
|
+
this.closeSuggestions()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
handleDelete = (item) => {
|
|
167
|
+
window.store.terminalCommandHistory.delete(item.command)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
handleSelect = (item) => {
|
|
171
|
+
const { activeTabId } = window.store
|
|
172
|
+
const terminal = refs.get('term-' + activeTabId)
|
|
173
|
+
if (!terminal) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// const titleElement = domEvent.target.closest('.ant-menu-title-content')
|
|
178
|
+
// const command = titleElement?.firstChild?.textContent
|
|
179
|
+
const { command } = item
|
|
180
|
+
const { cmd } = this.state
|
|
181
|
+
let txt = ''
|
|
182
|
+
if (cmd && command.startsWith(cmd)) {
|
|
183
|
+
txt = command.slice(cmd.length)
|
|
184
|
+
} else {
|
|
185
|
+
const pre = '\b'.repeat(cmd.length)
|
|
186
|
+
txt = pre + command
|
|
187
|
+
}
|
|
188
|
+
terminal.attachAddon._sendData(txt)
|
|
189
|
+
terminal.term.focus()
|
|
190
|
+
this.closeSuggestions()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
processCommands = (commands = [], type, uniqueCommands, res) => {
|
|
194
|
+
const { cmd } = this.state
|
|
195
|
+
commands
|
|
196
|
+
.filter(command => command && command.startsWith(cmd))
|
|
197
|
+
.forEach(command => {
|
|
198
|
+
if (!uniqueCommands.has(command)) {
|
|
199
|
+
uniqueCommands.add(command)
|
|
200
|
+
res.push({
|
|
201
|
+
id: uid(),
|
|
202
|
+
command,
|
|
203
|
+
type
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getSuggestions = () => {
|
|
210
|
+
const uniqueCommands = new Set()
|
|
211
|
+
const {
|
|
212
|
+
history = [],
|
|
213
|
+
batch = [],
|
|
214
|
+
quick = []
|
|
215
|
+
} = this.props.suggestions
|
|
216
|
+
const res = []
|
|
217
|
+
this.state.aiSuggestions
|
|
218
|
+
.forEach(item => {
|
|
219
|
+
if (!uniqueCommands.has(item.command)) {
|
|
220
|
+
uniqueCommands.add(item.command)
|
|
221
|
+
}
|
|
222
|
+
res.push({
|
|
223
|
+
id: uid(),
|
|
224
|
+
...item
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
this.processCommands(history, 'H', uniqueCommands, res)
|
|
228
|
+
this.processCommands(batch, 'B', uniqueCommands, res)
|
|
229
|
+
this.processCommands(quick, 'Q', uniqueCommands, res)
|
|
230
|
+
return this.state.reverse ? res.reverse() : res
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
renderAIIcon () {
|
|
234
|
+
const e = window.translate
|
|
235
|
+
const {
|
|
236
|
+
loadingAiSuggestions
|
|
237
|
+
} = this.state
|
|
238
|
+
if (loadingAiSuggestions) {
|
|
239
|
+
return (
|
|
240
|
+
<>
|
|
241
|
+
<LoadingOutlined /> {e('getAiSuggestions')}
|
|
242
|
+
</>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
const aiProps = {
|
|
246
|
+
onClick: this.getAiSuggestions,
|
|
247
|
+
className: 'pointer'
|
|
248
|
+
}
|
|
249
|
+
return (
|
|
250
|
+
<div {...aiProps}>
|
|
251
|
+
{e('getAiSuggestions')}
|
|
252
|
+
</div>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
renderSticky (pos) {
|
|
257
|
+
const {
|
|
258
|
+
reverse
|
|
259
|
+
} = this.state
|
|
260
|
+
if (
|
|
261
|
+
(pos === 'top' && !reverse) ||
|
|
262
|
+
(pos === 'bottom' && reverse)
|
|
263
|
+
) {
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
return (
|
|
267
|
+
<div className='terminal-suggestions-sticky'>
|
|
268
|
+
{this.renderAIIcon()}
|
|
269
|
+
</div>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
render () {
|
|
274
|
+
const { showSuggestions, cursorPosition, reverse } = this.state
|
|
275
|
+
if (!showSuggestions) {
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
const suggestions = this.getSuggestions()
|
|
279
|
+
const cls = classnames('terminal-suggestions-wrap', {
|
|
280
|
+
reverse
|
|
281
|
+
})
|
|
282
|
+
return (
|
|
283
|
+
<div className={cls} style={cursorPosition}>
|
|
284
|
+
{this.renderSticky('top')}
|
|
285
|
+
<div className='terminal-suggestions-list'>
|
|
286
|
+
{
|
|
287
|
+
suggestions.map(item => {
|
|
288
|
+
return (
|
|
289
|
+
<SuggestionItem
|
|
290
|
+
key={item.id}
|
|
291
|
+
item={item}
|
|
292
|
+
onSelect={this.handleSelect}
|
|
293
|
+
onDelete={this.handleDelete}
|
|
294
|
+
/>
|
|
295
|
+
)
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
</div>
|
|
299
|
+
{this.renderSticky('bottom')}
|
|
300
|
+
</div>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -50,7 +50,7 @@ import AIIcon from '../icons/ai-icon.jsx'
|
|
|
50
50
|
import { formatBytes } from '../../common/byte-format.js'
|
|
51
51
|
import * as fs from './fs.js'
|
|
52
52
|
import iconsMap from '../sys-menu/icons-map.jsx'
|
|
53
|
-
import { refs } from '../common/ref.js'
|
|
53
|
+
import { refs, refsStatic } from '../common/ref.js'
|
|
54
54
|
import createDefaultLogPath from '../../common/default-log-path.js'
|
|
55
55
|
|
|
56
56
|
const e = window.translate
|
|
@@ -75,10 +75,7 @@ class Term extends Component {
|
|
|
75
75
|
componentDidMount () {
|
|
76
76
|
this.initTerminal()
|
|
77
77
|
if (this.props.tab.enableSsh === false) {
|
|
78
|
-
|
|
79
|
-
document.querySelector('.session-current .term-sftp-tabs .type-tab.sftp') ||
|
|
80
|
-
document.querySelector('.session-current .term-sftp-tabs .type-tab.fileManager')
|
|
81
|
-
).click()
|
|
78
|
+
this.props.tab.pane = paneMap.fileManager
|
|
82
79
|
}
|
|
83
80
|
}
|
|
84
81
|
|
|
@@ -255,7 +252,7 @@ clear\r`
|
|
|
255
252
|
|
|
256
253
|
isActiveTerminal = () => {
|
|
257
254
|
return this.props.tab.id === this.props.activeTabId &&
|
|
258
|
-
this.props.pane === paneMap.terminal
|
|
255
|
+
this.props.tab.pane === paneMap.terminal
|
|
259
256
|
}
|
|
260
257
|
|
|
261
258
|
clearShortcut = (e) => {
|
|
@@ -560,7 +557,7 @@ clear\r`
|
|
|
560
557
|
}
|
|
561
558
|
const r = []
|
|
562
559
|
for (const filePath of files) {
|
|
563
|
-
const stat = await getLocalFileInfo(filePath)
|
|
560
|
+
const stat = await getLocalFileInfo(filePath).catch(console.log)
|
|
564
561
|
r.push({ ...stat, filePath })
|
|
565
562
|
}
|
|
566
563
|
return r
|
|
@@ -822,14 +819,58 @@ clear\r`
|
|
|
822
819
|
this.props.setCwd(cwd, this.state.id)
|
|
823
820
|
}
|
|
824
821
|
|
|
822
|
+
getCursorPosition = () => {
|
|
823
|
+
if (!this.term) return null
|
|
824
|
+
|
|
825
|
+
// Get the active buffer and cursor position
|
|
826
|
+
const buffer = this.term.buffer.active
|
|
827
|
+
const cursorRow = buffer.cursorY
|
|
828
|
+
const cursorCol = buffer.cursorX
|
|
829
|
+
|
|
830
|
+
// Get dimensions from term element
|
|
831
|
+
const termElement = this.term.element
|
|
832
|
+
if (!termElement) return null
|
|
833
|
+
|
|
834
|
+
// Get the exact position of the terminal element
|
|
835
|
+
const termRect = termElement.getBoundingClientRect()
|
|
836
|
+
|
|
837
|
+
// Calculate cell dimensions
|
|
838
|
+
const cellWidth = termRect.width / this.term.cols
|
|
839
|
+
const cellHeight = termRect.height / this.term.rows
|
|
840
|
+
|
|
841
|
+
// Calculate absolute position relative to terminal element
|
|
842
|
+
const left = Math.floor(termRect.left + (cursorCol * cellWidth))
|
|
843
|
+
const top = Math.floor(termRect.top + ((cursorRow + 1) * cellHeight))
|
|
844
|
+
|
|
845
|
+
return {
|
|
846
|
+
cellWidth,
|
|
847
|
+
cellHeight,
|
|
848
|
+
left,
|
|
849
|
+
top
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
closeSuggestions = () => {
|
|
854
|
+
refsStatic
|
|
855
|
+
.get('terminal-suggestions')
|
|
856
|
+
?.closeSuggestions()
|
|
857
|
+
}
|
|
858
|
+
|
|
825
859
|
onData = (d) => {
|
|
826
860
|
if (this.cmdAddon) {
|
|
827
861
|
this.cmdAddon.handleData(d)
|
|
828
862
|
}
|
|
863
|
+
const data = this.getCmd().trim()
|
|
829
864
|
if (!d.includes('\r')) {
|
|
830
865
|
delete this.userTypeExit
|
|
866
|
+
const cursorPos = this.getCursorPosition()
|
|
867
|
+
if (this.props.config.showCmdSuggestions && data.length > 1) {
|
|
868
|
+
refsStatic
|
|
869
|
+
.get('terminal-suggestions')
|
|
870
|
+
?.openSuggestions(cursorPos, data)
|
|
871
|
+
}
|
|
831
872
|
} else {
|
|
832
|
-
|
|
873
|
+
this.closeSuggestions()
|
|
833
874
|
if (this.term.buffer.active.type !== 'alternate') {
|
|
834
875
|
this.timers.getCwd = setTimeout(this.getCwd, 200)
|
|
835
876
|
}
|
|
@@ -837,6 +878,7 @@ clear\r`
|
|
|
837
878
|
'exit',
|
|
838
879
|
'logout'
|
|
839
880
|
]
|
|
881
|
+
window.store.addCmdHistory(data)
|
|
840
882
|
if (exitCmds.includes(data)) {
|
|
841
883
|
this.userTypeExit = true
|
|
842
884
|
this.timers.userTypeExit = setTimeout(() => {
|
|
@@ -1083,6 +1125,7 @@ clear\r`
|
|
|
1083
1125
|
socket.onclose = this.oncloseSocket
|
|
1084
1126
|
socket.onerror = this.onerrorSocket
|
|
1085
1127
|
this.socket = socket
|
|
1128
|
+
this.initSocketEvents()
|
|
1086
1129
|
this.term = term
|
|
1087
1130
|
socket.onopen = () => {
|
|
1088
1131
|
this.initAttachAddon()
|
|
@@ -1106,6 +1149,43 @@ clear\r`
|
|
|
1106
1149
|
)
|
|
1107
1150
|
}
|
|
1108
1151
|
|
|
1152
|
+
initSocketEvents = () => {
|
|
1153
|
+
const originalSend = this.socket.send
|
|
1154
|
+
this.socket.send = (data) => {
|
|
1155
|
+
// Call original send first
|
|
1156
|
+
originalSend.call(this.socket, data)
|
|
1157
|
+
|
|
1158
|
+
// Broadcast to other terminals
|
|
1159
|
+
this.broadcastSocketData(data)
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
canReceiveBroadcast = (termRef) => {
|
|
1164
|
+
const tabId = termRef.props?.tab?.id
|
|
1165
|
+
const isActiveInBatch = termRef.props.currentBatchTabId === tabId
|
|
1166
|
+
return (
|
|
1167
|
+
isActiveInBatch &&
|
|
1168
|
+
termRef.socket &&
|
|
1169
|
+
termRef.props?.tab.pane === paneMap.terminal
|
|
1170
|
+
)
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
broadcastSocketData = (data) => {
|
|
1174
|
+
if (!this.isActiveTerminal() || !this.props.broadcastInput) {
|
|
1175
|
+
return
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
window.refs.forEach((termRef, refId) => {
|
|
1179
|
+
if (
|
|
1180
|
+
refId !== this.id &&
|
|
1181
|
+
refId.startsWith('term-') &&
|
|
1182
|
+
this.canReceiveBroadcast(termRef)
|
|
1183
|
+
) {
|
|
1184
|
+
termRef.socket.send(data)
|
|
1185
|
+
}
|
|
1186
|
+
})
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1109
1189
|
onResize = throttle(() => {
|
|
1110
1190
|
const cid = this.props.currentBatchTabId
|
|
1111
1191
|
const tid = this.props.tab?.id
|
|
@@ -77,3 +77,61 @@
|
|
|
77
77
|
background main-light
|
|
78
78
|
.batch-input-wrap
|
|
79
79
|
width calc(100% - 80px)
|
|
80
|
+
|
|
81
|
+
.terminal-suggestions-wrap
|
|
82
|
+
position absolute
|
|
83
|
+
z-index 100
|
|
84
|
+
color text
|
|
85
|
+
max-height 300px
|
|
86
|
+
max-width 300px
|
|
87
|
+
min-width 200px
|
|
88
|
+
box-shadow 0px 0px 3px 3px main-light
|
|
89
|
+
display flex
|
|
90
|
+
flex-direction column
|
|
91
|
+
background main
|
|
92
|
+
border-radius 4px
|
|
93
|
+
&.reverse .terminal-suggestions-list
|
|
94
|
+
border-top 1px solid main-light
|
|
95
|
+
border-bottom none
|
|
96
|
+
|
|
97
|
+
.terminal-suggestions-list
|
|
98
|
+
flex 1
|
|
99
|
+
overflow-x hidden
|
|
100
|
+
overflow-y auto
|
|
101
|
+
max-height 268px
|
|
102
|
+
max-width 300px
|
|
103
|
+
border-bottom 1px solid main-light
|
|
104
|
+
|
|
105
|
+
.terminal-suggestions-sticky
|
|
106
|
+
flex-shrink 0
|
|
107
|
+
height 32px
|
|
108
|
+
padding 0 10px
|
|
109
|
+
line-height 32px
|
|
110
|
+
|
|
111
|
+
.suggestion-item
|
|
112
|
+
display flex
|
|
113
|
+
align-items center
|
|
114
|
+
padding 5px 10px
|
|
115
|
+
cursor pointer
|
|
116
|
+
color text
|
|
117
|
+
&:hover
|
|
118
|
+
background-color main-light
|
|
119
|
+
.suggestion-delete
|
|
120
|
+
visibility visible
|
|
121
|
+
|
|
122
|
+
.suggestion-command
|
|
123
|
+
flex-grow 1
|
|
124
|
+
white-space nowrap
|
|
125
|
+
overflow hidden
|
|
126
|
+
text-overflow ellipsis
|
|
127
|
+
|
|
128
|
+
.suggestion-type
|
|
129
|
+
margin-left 5px
|
|
130
|
+
font-size 0.8em
|
|
131
|
+
opacity .8
|
|
132
|
+
&:hover
|
|
133
|
+
opacity 1
|
|
134
|
+
|
|
135
|
+
.suggestion-delete
|
|
136
|
+
margin-left 5px
|
|
137
|
+
visibility hidden
|
|
@@ -43,7 +43,7 @@ export default class TerminalInfoContent extends PureComponent {
|
|
|
43
43
|
return null
|
|
44
44
|
}
|
|
45
45
|
return (
|
|
46
|
-
|
|
46
|
+
<>
|
|
47
47
|
<TerminalInfoBase {...props} {...state} />
|
|
48
48
|
<TerminalInfoUp {...props} {...state} />
|
|
49
49
|
<TerminalInfoResource
|
|
@@ -57,7 +57,7 @@ export default class TerminalInfoContent extends PureComponent {
|
|
|
57
57
|
<TerminalInfoNetwork {...props} {...state} />
|
|
58
58
|
<TerminalInfoDisk {...props} {...state} />
|
|
59
59
|
<RunCmd {...props} setState={this.setStateRef} />
|
|
60
|
-
|
|
60
|
+
</>
|
|
61
61
|
)
|
|
62
62
|
}
|
|
63
63
|
}
|
package/client/store/common.js
CHANGED
|
@@ -10,12 +10,16 @@ import {
|
|
|
10
10
|
leftSidebarWidthKey,
|
|
11
11
|
rightSidebarWidthKey,
|
|
12
12
|
dismissDelKeyTipLsKey,
|
|
13
|
-
connectionMap
|
|
13
|
+
connectionMap,
|
|
14
|
+
settingMap,
|
|
15
|
+
settingAiId
|
|
14
16
|
} from '../common/constants'
|
|
15
17
|
import * as ls from '../common/safe-local-storage'
|
|
16
18
|
import { refs, refsStatic } from '../components/common/ref'
|
|
17
19
|
import { action } from 'manate'
|
|
18
20
|
import deepCopy from 'json-deep-copy'
|
|
21
|
+
import { aiConfigsArr } from '../components/ai/ai-config-props'
|
|
22
|
+
import settingList from '../common/setting-list'
|
|
19
23
|
|
|
20
24
|
const e = window.translate
|
|
21
25
|
const { assign } = Object
|
|
@@ -49,7 +53,12 @@ export default Store => {
|
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
Store.prototype.toggleAIConfig = function () {
|
|
52
|
-
|
|
56
|
+
const { store } = window
|
|
57
|
+
store.storeAssign({
|
|
58
|
+
settingTab: settingMap.setting
|
|
59
|
+
})
|
|
60
|
+
store.setSettingItem(settingList().find(d => d.id === settingAiId))
|
|
61
|
+
store.openSettingModal()
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
Store.prototype.onResize = debounce(async function () {
|
|
@@ -303,4 +312,20 @@ export default Store => {
|
|
|
303
312
|
profiles.splice(i, 1, np)
|
|
304
313
|
}
|
|
305
314
|
}
|
|
315
|
+
|
|
316
|
+
Store.prototype.aiConfigMissing = function () {
|
|
317
|
+
return aiConfigsArr.some(k => !window.store.config[k])
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
Store.prototype.addCmdHistory = action(function (cmd) {
|
|
321
|
+
const { terminalCommandHistory } = window.store
|
|
322
|
+
terminalCommandHistory.add(cmd)
|
|
323
|
+
if (terminalCommandHistory.size > 100) {
|
|
324
|
+
// Delete oldest 20 items when history exceeds 100
|
|
325
|
+
const values = Array.from(terminalCommandHistory.values())
|
|
326
|
+
for (let i = 0; i < 20 && i < values.length; i++) {
|
|
327
|
+
terminalCommandHistory.delete(values[i])
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
})
|
|
306
331
|
}
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
resolutionsLsKey,
|
|
22
22
|
aiChatHistoryKey,
|
|
23
23
|
syncServerDataKey,
|
|
24
|
-
splitMap
|
|
24
|
+
splitMap,
|
|
25
|
+
cmdHistoryKey
|
|
25
26
|
} from '../common/constants'
|
|
26
27
|
import { buildDefaultThemes } from '../common/terminal-theme'
|
|
27
28
|
import * as ls from '../common/safe-local-storage'
|
|
@@ -72,6 +73,7 @@ export default () => {
|
|
|
72
73
|
addressBookmarksLocal: ls.getItemJSON(localAddrBookmarkLsKey, []),
|
|
73
74
|
openResolutionEdit: false,
|
|
74
75
|
resolutions: ls.getItemJSON(resolutionsLsKey, []),
|
|
76
|
+
terminalCommandHistory: new Set(ls.getItemJSON(cmdHistoryKey, [])),
|
|
75
77
|
|
|
76
78
|
// init session control
|
|
77
79
|
selectedSessions: [],
|
|
@@ -79,7 +81,6 @@ export default () => {
|
|
|
79
81
|
|
|
80
82
|
// batch input selected tab ids
|
|
81
83
|
_batchInputSelectedTabIds: new Set(),
|
|
82
|
-
showAIConfig: false,
|
|
83
84
|
aiChatHistory: ls.getItemJSON(aiChatHistoryKey, []),
|
|
84
85
|
|
|
85
86
|
// sftp
|
|
@@ -114,7 +115,6 @@ export default () => {
|
|
|
114
115
|
rightPanelWidth: parseInt(ls.getItem(rightSidebarWidthKey), 10) || 500,
|
|
115
116
|
|
|
116
117
|
// for settings related
|
|
117
|
-
_setting: '',
|
|
118
118
|
settingItem: initSettingItem([], settingMap.bookmarks),
|
|
119
119
|
settingTab: settingMap.bookmarks, // setting tab
|
|
120
120
|
bookmarkId: undefined,
|
package/client/store/item.js
CHANGED
|
@@ -6,6 +6,7 @@ import deepCopy from 'json-deep-copy'
|
|
|
6
6
|
import {
|
|
7
7
|
settingMap
|
|
8
8
|
} from '../common/constants'
|
|
9
|
+
import settingList from '../common/setting-list'
|
|
9
10
|
import getInitItem from '../common/init-setting-item'
|
|
10
11
|
|
|
11
12
|
export default Store => {
|
|
@@ -72,7 +73,7 @@ export default Store => {
|
|
|
72
73
|
|
|
73
74
|
Store.prototype.getItems = function (type) {
|
|
74
75
|
if (type === 'setting') {
|
|
75
|
-
return
|
|
76
|
+
return settingList()
|
|
76
77
|
}
|
|
77
78
|
return window.store[type]
|
|
78
79
|
}
|
package/client/store/setting.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { buildNewTheme } from '../common/terminal-theme'
|
|
16
16
|
import getInitItem from '../common/init-setting-item'
|
|
17
17
|
import newTerm from '../common/new-terminal'
|
|
18
|
+
import settingList from '../common/setting-list'
|
|
18
19
|
|
|
19
20
|
const e = window.translate
|
|
20
21
|
|
|
@@ -101,7 +102,7 @@ export default Store => {
|
|
|
101
102
|
const { store } = window
|
|
102
103
|
if (
|
|
103
104
|
store.settingTab === settingMap.setting &&
|
|
104
|
-
store.settingItem.id ===
|
|
105
|
+
store.settingItem.id === settingList()[0].id &&
|
|
105
106
|
store.showModal === modals.setting
|
|
106
107
|
) {
|
|
107
108
|
return store.hideSettingModal()
|
|
@@ -109,7 +110,7 @@ export default Store => {
|
|
|
109
110
|
store.storeAssign({
|
|
110
111
|
settingTab: settingMap.setting
|
|
111
112
|
})
|
|
112
|
-
store.setSettingItem(
|
|
113
|
+
store.setSettingItem(settingList().find(d => d.id === settingSyncId))
|
|
113
114
|
store.openSettingModal()
|
|
114
115
|
}
|
|
115
116
|
|