@electerm/electerm-react 2.10.27 → 2.11.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/default-setting.js +1 -0
- package/client/common/fetch.jsx +1 -1
- package/client/common/has-active-input.js +1 -1
- package/client/common/parse-quick-connect.js +438 -0
- package/client/components/ai/ai-chat-history-item.jsx +1 -1
- package/client/components/ai/ai-config.jsx +1 -1
- package/client/components/bg/css-overwrite.jsx +5 -1
- package/client/components/bookmark-form/ai-bookmark-form.jsx +40 -3
- package/client/components/bookmark-form/bookmark-form.styl +21 -0
- package/client/components/bookmark-form/bookmark-from-history-modal.jsx +141 -0
- package/client/components/bookmark-form/tree-select.jsx +72 -23
- package/client/components/common/input-context-menu.jsx +13 -5
- package/client/components/main/main.jsx +3 -0
- package/client/components/rdp/rdp-session.jsx +4 -8
- package/client/components/rdp/rdp.styl +15 -0
- package/client/components/setting-panel/bookmark-tree-list.jsx +1 -0
- package/client/components/setting-panel/deep-link-control.jsx +1 -1
- package/client/components/setting-panel/list.styl +10 -4
- package/client/components/setting-panel/setting-terminal.jsx +3 -2
- package/client/components/sftp/list-table-ui.jsx +29 -2
- package/client/components/sftp/paged-list.jsx +3 -8
- package/client/components/sidebar/history-item.jsx +13 -1
- package/client/components/sidebar/index.jsx +13 -10
- package/client/components/spice/spice.styl +7 -0
- package/client/components/tabs/add-btn-menu.jsx +2 -0
- package/client/components/tabs/no-session.jsx +25 -9
- package/client/components/tabs/no-session.styl +21 -0
- package/client/components/tabs/quick-connect.jsx +127 -0
- package/client/components/tabs/tabs.styl +1 -19
- package/client/components/terminal/highlight-addon.js +11 -0
- package/client/components/terminal/terminal.jsx +16 -1
- package/client/components/terminal/trzsz-client.js +6 -0
- package/client/components/terminal/xterm-loader.js +11 -0
- package/client/components/terminal-info/run-cmd.jsx +2 -1
- package/client/store/load-data.js +5 -1
- package/client/store/sync.js +1 -0
- package/client/store/tab.js +9 -0
- package/package.json +1 -1
package/client/common/fetch.jsx
CHANGED
|
@@ -5,6 +5,6 @@ export default function hasActiveInput (className = 'ant-input-search') {
|
|
|
5
5
|
activeElement.tagName === 'TEXTAREA'
|
|
6
6
|
)
|
|
7
7
|
const hasClass = className ? activeElement.classList.contains(className) : true
|
|
8
|
-
const hasInputDropDown = document.querySelector('.
|
|
8
|
+
const hasInputDropDown = document.querySelector('.input-context-menu')
|
|
9
9
|
return (isInput && hasClass) || hasInputDropDown
|
|
10
10
|
}
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick Connect String Parser
|
|
3
|
+
* Parses connection strings according to temp/quick-connect.wiki.md specification
|
|
4
|
+
*
|
|
5
|
+
* Supported Protocols: ssh, telnet, vnc, rdp, spice, serial, ftp, http, https, electerm
|
|
6
|
+
*
|
|
7
|
+
* Basic Format:
|
|
8
|
+
* protocol://[username:password@]host[:port]?anyQueryParam=anyValue&opts={"key":"value"}
|
|
9
|
+
*
|
|
10
|
+
* electerm:// Format (default type is ssh):
|
|
11
|
+
* electerm://[username:password@]host[:port]?type=ssh&anyQueryParam=anyValue
|
|
12
|
+
* electerm://host?type=telnet
|
|
13
|
+
* electerm://user@host:22?type=vnc
|
|
14
|
+
*
|
|
15
|
+
* Shortcut Format (SSH default):
|
|
16
|
+
* user@host
|
|
17
|
+
* user@host:22
|
|
18
|
+
* 192.168.1.100
|
|
19
|
+
* 192.168.1.100:22
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const SUPPORTED_PROTOCOLS = ['ssh', 'telnet', 'vnc', 'rdp', 'spice', 'serial', 'ftp', 'http', 'https', 'electerm']
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default ports for each protocol
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_PORTS = {
|
|
28
|
+
ssh: 22,
|
|
29
|
+
telnet: 23,
|
|
30
|
+
vnc: 5900,
|
|
31
|
+
rdp: 3389,
|
|
32
|
+
spice: 5900,
|
|
33
|
+
serial: undefined, // Serial doesn't have a default port
|
|
34
|
+
ftp: 21,
|
|
35
|
+
http: 80,
|
|
36
|
+
https: 443,
|
|
37
|
+
electerm: 22 // electerm defaults to SSH port
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default values for each protocol type
|
|
42
|
+
* Based on src/client/components/bookmark-form/config
|
|
43
|
+
*/
|
|
44
|
+
const TYPE_DEFAULT_VALUES = {
|
|
45
|
+
ssh: {
|
|
46
|
+
port: 22,
|
|
47
|
+
enableSsh: true,
|
|
48
|
+
enableSftp: true,
|
|
49
|
+
useSshAgent: true,
|
|
50
|
+
term: 'xterm-256color',
|
|
51
|
+
encode: 'utf-8',
|
|
52
|
+
envLang: 'en_US.UTF-8'
|
|
53
|
+
},
|
|
54
|
+
telnet: {
|
|
55
|
+
port: 23
|
|
56
|
+
},
|
|
57
|
+
vnc: {
|
|
58
|
+
port: 5900,
|
|
59
|
+
viewOnly: false,
|
|
60
|
+
clipViewport: false,
|
|
61
|
+
scaleViewport: true,
|
|
62
|
+
qualityLevel: 3,
|
|
63
|
+
compressionLevel: 1,
|
|
64
|
+
shared: true
|
|
65
|
+
},
|
|
66
|
+
rdp: {
|
|
67
|
+
port: 3389
|
|
68
|
+
},
|
|
69
|
+
spice: {
|
|
70
|
+
port: 5900,
|
|
71
|
+
viewOnly: false,
|
|
72
|
+
scaleViewport: true
|
|
73
|
+
},
|
|
74
|
+
serial: {
|
|
75
|
+
baudRate: 9600,
|
|
76
|
+
dataBits: 8,
|
|
77
|
+
lock: true,
|
|
78
|
+
stopBits: 1,
|
|
79
|
+
parity: 'none',
|
|
80
|
+
rtscts: false,
|
|
81
|
+
xon: false,
|
|
82
|
+
xoff: false,
|
|
83
|
+
xany: false,
|
|
84
|
+
term: 'xterm-256color',
|
|
85
|
+
displayRaw: false
|
|
86
|
+
},
|
|
87
|
+
ftp: {
|
|
88
|
+
port: 21,
|
|
89
|
+
encode: 'utf-8',
|
|
90
|
+
secure: false
|
|
91
|
+
},
|
|
92
|
+
web: {},
|
|
93
|
+
local: {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse a quick connect string into connection options
|
|
98
|
+
* @param {string} str - The connection string
|
|
99
|
+
* @returns {object|null} - Parsed options or null if invalid
|
|
100
|
+
*/
|
|
101
|
+
function parseQuickConnect (str) {
|
|
102
|
+
if (!str || typeof str !== 'string') {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const trimmed = str.trim()
|
|
107
|
+
if (!trimmed) {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Detect protocol
|
|
113
|
+
const protocolMatch = trimmed.match(/^(ssh|telnet|vnc|rdp|spice|serial|ftp|https?|electerm):\/\//i)
|
|
114
|
+
|
|
115
|
+
let protocol = ''
|
|
116
|
+
let connectionString = ''
|
|
117
|
+
let originalProtocol = 'ssh'
|
|
118
|
+
|
|
119
|
+
if (protocolMatch) {
|
|
120
|
+
originalProtocol = protocolMatch[1].toLowerCase()
|
|
121
|
+
protocol = originalProtocol
|
|
122
|
+
// Normalize http/https to web
|
|
123
|
+
if (protocol === 'http' || protocol === 'https') {
|
|
124
|
+
protocol = 'web'
|
|
125
|
+
}
|
|
126
|
+
connectionString = trimmed.slice(protocolMatch[0].length)
|
|
127
|
+
} else {
|
|
128
|
+
// Shortcut format - default to SSH
|
|
129
|
+
// Match user@host or user@host:port or just host or host:port
|
|
130
|
+
// Use last colon to determine port for host:port format
|
|
131
|
+
if (/^[\w.-]+@[\w.-]+/.test(trimmed)) {
|
|
132
|
+
// user@host or user@host:port
|
|
133
|
+
protocol = 'ssh'
|
|
134
|
+
connectionString = trimmed
|
|
135
|
+
} else if (/^[\w.-]+:.*:[\d]+$/.test(trimmed)) {
|
|
136
|
+
// host:port format with colons in hostname (e.g., localhost:23344, zxd:localhost:23344)
|
|
137
|
+
// Check if the last colon is followed by digits (port number)
|
|
138
|
+
protocol = 'ssh'
|
|
139
|
+
connectionString = trimmed
|
|
140
|
+
} else if (/^[\w.-]+:[\d]+$/.test(trimmed)) {
|
|
141
|
+
// host:port (no username, simple format like host:22)
|
|
142
|
+
protocol = 'ssh'
|
|
143
|
+
connectionString = trimmed
|
|
144
|
+
} else if (/^[\w.-]+$/.test(trimmed)) {
|
|
145
|
+
// just host
|
|
146
|
+
protocol = 'ssh'
|
|
147
|
+
connectionString = trimmed
|
|
148
|
+
} else {
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!SUPPORTED_PROTOCOLS.includes(protocol) && protocol !== 'web') {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Extract opts from the connection string before parsing
|
|
158
|
+
let optsStr = ''
|
|
159
|
+
const optsMatch = connectionString.match(/[?&]opts=('|")(.+?)('|")$/)
|
|
160
|
+
if (!optsMatch) {
|
|
161
|
+
// Try without quotes
|
|
162
|
+
const optsMatchNoQuote = connectionString.match(/[?&]opts=(\{.+?\})$/)
|
|
163
|
+
if (optsMatchNoQuote) {
|
|
164
|
+
optsStr = optsMatchNoQuote[1]
|
|
165
|
+
connectionString = connectionString.slice(0, optsMatchNoQuote.index)
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
optsStr = optsMatch[2]
|
|
169
|
+
connectionString = connectionString.slice(0, optsMatch.index)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Extract query string for web type and electerm type
|
|
173
|
+
let queryStr = ''
|
|
174
|
+
const queryMatch = connectionString.match(/\?(.+)$/)
|
|
175
|
+
if (queryMatch) {
|
|
176
|
+
queryStr = queryMatch[1]
|
|
177
|
+
connectionString = connectionString.slice(0, queryMatch.index)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Parse username:password@host:port
|
|
181
|
+
// First, check if there's an @ for auth
|
|
182
|
+
let username = ''
|
|
183
|
+
let password = ''
|
|
184
|
+
let hostOrPath = ''
|
|
185
|
+
let port = ''
|
|
186
|
+
|
|
187
|
+
const atIndex = connectionString.indexOf('@')
|
|
188
|
+
if (atIndex !== -1) {
|
|
189
|
+
// Has auth
|
|
190
|
+
const authPart = connectionString.slice(0, atIndex)
|
|
191
|
+
const hostPart = connectionString.slice(atIndex + 1)
|
|
192
|
+
const colonIndex = authPart.indexOf(':')
|
|
193
|
+
if (colonIndex !== -1) {
|
|
194
|
+
username = authPart.slice(0, colonIndex)
|
|
195
|
+
password = authPart.slice(colonIndex + 1)
|
|
196
|
+
} else {
|
|
197
|
+
username = authPart
|
|
198
|
+
}
|
|
199
|
+
// Parse host:port from hostPart
|
|
200
|
+
const hostColonIndex = hostPart.lastIndexOf(':')
|
|
201
|
+
if (hostColonIndex !== -1) {
|
|
202
|
+
hostOrPath = hostPart.slice(0, hostColonIndex)
|
|
203
|
+
port = hostPart.slice(hostColonIndex + 1)
|
|
204
|
+
} else {
|
|
205
|
+
hostOrPath = hostPart
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// No @ sign - check for special case: protocol://password:host (e.g., spice://password:host)
|
|
209
|
+
// This only applies to spice protocol
|
|
210
|
+
if (protocol === 'spice') {
|
|
211
|
+
// Count colons in the connection string
|
|
212
|
+
const colonCount = (connectionString.match(/:/g) || []).length
|
|
213
|
+
|
|
214
|
+
if (colonCount >= 2) {
|
|
215
|
+
// Multiple colons - could be password:host:port or host:port with IP
|
|
216
|
+
// Use lastIndexOf for port, then check if first part is password or IP
|
|
217
|
+
const lastColonIndex = connectionString.lastIndexOf(':')
|
|
218
|
+
const portCandidate = connectionString.slice(lastColonIndex + 1)
|
|
219
|
+
|
|
220
|
+
if (/^\d+$/.test(portCandidate)) {
|
|
221
|
+
// Last part is a port number
|
|
222
|
+
const hostPortPart = connectionString.slice(0, lastColonIndex)
|
|
223
|
+
const secondLastColonIndex = hostPortPart.lastIndexOf(':')
|
|
224
|
+
|
|
225
|
+
if (secondLastColonIndex !== -1) {
|
|
226
|
+
// There's another colon - first part could be password
|
|
227
|
+
const potentialPassword = hostPortPart.slice(0, secondLastColonIndex)
|
|
228
|
+
const hostPart = hostPortPart.slice(secondLastColonIndex + 1)
|
|
229
|
+
|
|
230
|
+
// Check if potentialPassword is NOT an IP/hostname
|
|
231
|
+
// An IP/hostname should contain dots, a password typically doesn't
|
|
232
|
+
// Also check it's not a simple number (port)
|
|
233
|
+
const isIPorHostname = (potentialPassword.includes('.') || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(potentialPassword))
|
|
234
|
+
|
|
235
|
+
if (isIPorHostname) {
|
|
236
|
+
// It's IP, no password
|
|
237
|
+
hostOrPath = hostPortPart
|
|
238
|
+
port = portCandidate
|
|
239
|
+
} else {
|
|
240
|
+
// It's password
|
|
241
|
+
password = potentialPassword
|
|
242
|
+
hostOrPath = hostPart
|
|
243
|
+
port = portCandidate
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// Only one colon before the port - it's host:port
|
|
247
|
+
hostOrPath = hostPortPart
|
|
248
|
+
port = portCandidate
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
// Last part is not a port
|
|
252
|
+
hostOrPath = connectionString
|
|
253
|
+
}
|
|
254
|
+
} else if (colonCount === 1) {
|
|
255
|
+
// Single colon - could be host:port or just a word with colon
|
|
256
|
+
const colonIndex = connectionString.indexOf(':')
|
|
257
|
+
const firstPart = connectionString.slice(0, colonIndex)
|
|
258
|
+
const secondPart = connectionString.slice(colonIndex + 1)
|
|
259
|
+
|
|
260
|
+
// Check if first part is an IP
|
|
261
|
+
const isIP = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(firstPart)
|
|
262
|
+
|
|
263
|
+
if (isIP) {
|
|
264
|
+
// IP with port
|
|
265
|
+
hostOrPath = firstPart
|
|
266
|
+
port = secondPart
|
|
267
|
+
} else if (/^\d+$/.test(secondPart)) {
|
|
268
|
+
// Just a word with port number
|
|
269
|
+
// This is likely password (for spice) or host without port
|
|
270
|
+
// For spice, treat first part as host (not password) since there's only one colon
|
|
271
|
+
hostOrPath = firstPart
|
|
272
|
+
port = secondPart
|
|
273
|
+
} else {
|
|
274
|
+
// host or hostname
|
|
275
|
+
hostOrPath = connectionString
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// No colon - just host
|
|
279
|
+
hostOrPath = connectionString
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
// Normal case - just host:port
|
|
283
|
+
const hostColonIndex = connectionString.lastIndexOf(':')
|
|
284
|
+
if (hostColonIndex !== -1) {
|
|
285
|
+
// Make sure it's a port number (all digits)
|
|
286
|
+
const potentialPort = connectionString.slice(hostColonIndex + 1)
|
|
287
|
+
if (/^\d+$/.test(potentialPort)) {
|
|
288
|
+
hostOrPath = connectionString.slice(0, hostColonIndex)
|
|
289
|
+
port = potentialPort
|
|
290
|
+
} else {
|
|
291
|
+
hostOrPath = connectionString
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
hostOrPath = connectionString
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!hostOrPath) {
|
|
300
|
+
return null
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Build base options
|
|
304
|
+
// For electerm protocol, we need to handle the type from query params
|
|
305
|
+
let finalProtocol = protocol
|
|
306
|
+
let webProtocol = originalProtocol // Store original for web type
|
|
307
|
+
|
|
308
|
+
// Handle electerm:// protocol - extract type from query params, default to ssh
|
|
309
|
+
if (originalProtocol === 'electerm') {
|
|
310
|
+
// Parse query params to get type
|
|
311
|
+
const params = new URLSearchParams(queryStr)
|
|
312
|
+
finalProtocol = params.get('type') || params.get('tp') || 'ssh'
|
|
313
|
+
|
|
314
|
+
// Validate the type is supported
|
|
315
|
+
if (!SUPPORTED_PROTOCOLS.includes(finalProtocol) && finalProtocol !== 'web') {
|
|
316
|
+
return null
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Normalize http/https to web
|
|
320
|
+
if (finalProtocol === 'http' || finalProtocol === 'https') {
|
|
321
|
+
webProtocol = finalProtocol // Store the http/https before normalizing
|
|
322
|
+
finalProtocol = 'web'
|
|
323
|
+
// Remove type/tp from query string for web URL construction
|
|
324
|
+
params.delete('type')
|
|
325
|
+
params.delete('tp')
|
|
326
|
+
queryStr = params.toString()
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
webProtocol = originalProtocol
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const opts = {
|
|
333
|
+
type: finalProtocol
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Handle different protocol types
|
|
337
|
+
if (finalProtocol === 'serial') {
|
|
338
|
+
// Serial: path is the port
|
|
339
|
+
opts.path = hostOrPath
|
|
340
|
+
if (port) {
|
|
341
|
+
opts.baudRate = parseInt(port, 10)
|
|
342
|
+
}
|
|
343
|
+
// Parse query params for serial (like baudRate)
|
|
344
|
+
if (queryStr) {
|
|
345
|
+
const params = new URLSearchParams(queryStr)
|
|
346
|
+
if (params.has('baudRate')) {
|
|
347
|
+
opts.baudRate = parseInt(params.get('baudRate'), 10)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} else if (finalProtocol === 'web') {
|
|
351
|
+
// Web: construct URL from protocol + host + port + query
|
|
352
|
+
let url = `${webProtocol}://${hostOrPath}`
|
|
353
|
+
if (port) {
|
|
354
|
+
// Add non-standard port to URL
|
|
355
|
+
const defaultPort = originalProtocol === 'https' ? 443 : 80
|
|
356
|
+
if (parseInt(port, 10) !== defaultPort) {
|
|
357
|
+
url += `:${port}`
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Add query string if present
|
|
361
|
+
if (queryStr) {
|
|
362
|
+
const separator = url.includes('?') ? '&' : '?'
|
|
363
|
+
url += `${separator}${queryStr}`
|
|
364
|
+
}
|
|
365
|
+
opts.url = url
|
|
366
|
+
} else {
|
|
367
|
+
// SSH, Telnet, VNC, RDP, Spice, FTP
|
|
368
|
+
opts.host = hostOrPath
|
|
369
|
+
if (port) {
|
|
370
|
+
opts.port = parseInt(port, 10)
|
|
371
|
+
}
|
|
372
|
+
if (username !== undefined && username !== '') {
|
|
373
|
+
opts.username = username
|
|
374
|
+
}
|
|
375
|
+
if (password !== undefined && password !== '') {
|
|
376
|
+
opts.password = password
|
|
377
|
+
}
|
|
378
|
+
// Parse query params for other protocols (like title)
|
|
379
|
+
if (queryStr) {
|
|
380
|
+
const params = new URLSearchParams(queryStr)
|
|
381
|
+
if (params.has('title')) {
|
|
382
|
+
opts.title = params.get('title')
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Parse opts JSON to extend params
|
|
388
|
+
if (optsStr) {
|
|
389
|
+
try {
|
|
390
|
+
const extraOpts = JSON.parse(optsStr)
|
|
391
|
+
Object.assign(opts, extraOpts)
|
|
392
|
+
} catch (err) {
|
|
393
|
+
console.error('Failed to parse opts:', err)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Apply default values for the protocol type
|
|
398
|
+
const typeDefaults = TYPE_DEFAULT_VALUES[finalProtocol]
|
|
399
|
+
if (typeDefaults) {
|
|
400
|
+
Object.keys(typeDefaults).forEach(key => {
|
|
401
|
+
// Only apply default if not already set
|
|
402
|
+
if (opts[key] === undefined) {
|
|
403
|
+
opts[key] = typeDefaults[key]
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return opts
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error('Error parsing quick connect string:', error)
|
|
411
|
+
return null
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get default port for a protocol
|
|
417
|
+
* @param {string} protocol - The protocol name
|
|
418
|
+
* @returns {number|undefined} - Default port or undefined
|
|
419
|
+
*/
|
|
420
|
+
function getDefaultPort (protocol) {
|
|
421
|
+
return DEFAULT_PORTS[protocol]
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get list of supported protocols
|
|
426
|
+
* @returns {string[]} - List of supported protocols
|
|
427
|
+
*/
|
|
428
|
+
function getSupportedProtocols () {
|
|
429
|
+
return [...SUPPORTED_PROTOCOLS]
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export {
|
|
433
|
+
parseQuickConnect,
|
|
434
|
+
getDefaultPort,
|
|
435
|
+
getSupportedProtocols,
|
|
436
|
+
SUPPORTED_PROTOCOLS,
|
|
437
|
+
DEFAULT_PORTS
|
|
438
|
+
}
|
|
@@ -118,7 +118,11 @@ export default class CssOverwrite extends Component {
|
|
|
118
118
|
const selector = `#container .sessions .session-${tab.id} .xterm-screen::before`
|
|
119
119
|
const styles = []
|
|
120
120
|
if (st === 'index') {
|
|
121
|
-
styles.push(
|
|
121
|
+
styles.push(
|
|
122
|
+
`content: '${tab.tabCount}'`,
|
|
123
|
+
'background-image: none',
|
|
124
|
+
'opacity: 0.1'
|
|
125
|
+
)
|
|
122
126
|
} else if (st === 'text') {
|
|
123
127
|
const text = bg.terminalBackgroundText || this.props.terminalBackgroundText || ''
|
|
124
128
|
const size = bg.terminalBackgroundTextSize || this.props.terminalBackgroundTextSize || 48
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
EditOutlined,
|
|
12
12
|
CopyOutlined,
|
|
13
13
|
DownloadOutlined,
|
|
14
|
-
EyeOutlined
|
|
14
|
+
EyeOutlined,
|
|
15
|
+
ThunderboltOutlined
|
|
15
16
|
} from '@ant-design/icons'
|
|
16
17
|
import SimpleEditor from '../text-editor/simple-editor'
|
|
17
18
|
import { copy } from '../../common/clipboard'
|
|
@@ -108,12 +109,12 @@ export default function AIBookmarkForm (props) {
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
function getGeneratedData () {
|
|
111
|
-
if (!editorText) return
|
|
112
|
+
if (!editorText) return []
|
|
112
113
|
let parsed = null
|
|
113
114
|
try {
|
|
114
115
|
parsed = fixBookmarkData(JSON.parse(editorText))
|
|
115
116
|
} catch (err) {
|
|
116
|
-
return
|
|
117
|
+
return []
|
|
117
118
|
}
|
|
118
119
|
if (!parsed) return []
|
|
119
120
|
return Array.isArray(parsed) ? parsed : [parsed]
|
|
@@ -166,6 +167,29 @@ export default function AIBookmarkForm (props) {
|
|
|
166
167
|
setShowConfirm(false)
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
const handleQuickConnect = () => {
|
|
171
|
+
const parsed = getGeneratedData()
|
|
172
|
+
if (!parsed.length || !parsed[0]) {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
const bm = parsed[0]
|
|
176
|
+
// Create a new tab with quick connect options
|
|
177
|
+
const { store } = window
|
|
178
|
+
|
|
179
|
+
// Close the setting panel first
|
|
180
|
+
store.hideSettingModal()
|
|
181
|
+
|
|
182
|
+
const tabOptions = {
|
|
183
|
+
...bm,
|
|
184
|
+
from: 'quickConnect'
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
store.addTab(tabOptions)
|
|
188
|
+
setShowConfirm(false)
|
|
189
|
+
setDescription('')
|
|
190
|
+
message.success(e('Done'))
|
|
191
|
+
}
|
|
192
|
+
|
|
169
193
|
const handleCancel = () => {
|
|
170
194
|
if (onCancel) {
|
|
171
195
|
onCancel()
|
|
@@ -245,6 +269,18 @@ export default function AIBookmarkForm (props) {
|
|
|
245
269
|
loading
|
|
246
270
|
}
|
|
247
271
|
|
|
272
|
+
function renderQuickConnectBtn () {
|
|
273
|
+
const parsed = getGeneratedData()
|
|
274
|
+
if (!parsed.length || !parsed[0] || parsed.length > 1) {
|
|
275
|
+
return null
|
|
276
|
+
}
|
|
277
|
+
return (
|
|
278
|
+
<Button onClick={handleQuickConnect} icon={<ThunderboltOutlined />}>
|
|
279
|
+
{e('quickConnect')}
|
|
280
|
+
</Button>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
248
284
|
const modalProps = {
|
|
249
285
|
title: e('confirmBookmarkData') || 'Confirm Bookmark Data',
|
|
250
286
|
open: showConfirm,
|
|
@@ -254,6 +290,7 @@ export default function AIBookmarkForm (props) {
|
|
|
254
290
|
<Button onClick={handleCancelConfirm}>
|
|
255
291
|
<CloseOutlined /> {e('cancel')}
|
|
256
292
|
</Button>
|
|
293
|
+
{renderQuickConnectBtn()}
|
|
257
294
|
<Button type='primary' onClick={handleConfirm}>
|
|
258
295
|
<CheckOutlined /> {e('confirm')}
|
|
259
296
|
</Button>
|
|
@@ -15,3 +15,24 @@
|
|
|
15
15
|
display inline-block
|
|
16
16
|
.number-input
|
|
17
17
|
min-width 210px
|
|
18
|
+
|
|
19
|
+
.tree-select-wrapper
|
|
20
|
+
display flex
|
|
21
|
+
flex-direction column
|
|
22
|
+
height 100%
|
|
23
|
+
overflow hidden
|
|
24
|
+
|
|
25
|
+
.tree-select-header
|
|
26
|
+
flex-shrink 0
|
|
27
|
+
|
|
28
|
+
.tree-select-content
|
|
29
|
+
flex 1
|
|
30
|
+
overflow auto
|
|
31
|
+
margin-top 8px
|
|
32
|
+
|
|
33
|
+
.bookmark-json-preview
|
|
34
|
+
padding 12px
|
|
35
|
+
max-height 300px
|
|
36
|
+
overflow auto
|
|
37
|
+
font-size 12px
|
|
38
|
+
font-family monospace
|