@electerm/electerm-react 2.8.16 → 2.10.26
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 +3 -3
- package/client/common/pre.js +1 -120
- package/client/components/ai/ai-config.jsx +26 -3
- package/client/components/ai/ai-history-item.jsx +46 -0
- package/client/components/ai/ai-history.jsx +104 -0
- package/client/components/bookmark-form/ai-bookmark-form.jsx +338 -0
- package/client/components/bookmark-form/bookmark-form.styl +1 -1
- package/client/components/bookmark-form/bookmark-schema.js +192 -0
- package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
- package/client/components/bookmark-form/common/category-select.jsx +2 -4
- package/client/components/bookmark-form/common/fields.jsx +0 -10
- package/client/components/bookmark-form/config/ftp.js +2 -0
- package/client/components/bookmark-form/config/rdp.js +0 -1
- package/client/components/bookmark-form/config/session-config.js +3 -1
- package/client/components/bookmark-form/config/spice.js +43 -0
- package/client/components/bookmark-form/config/vnc.js +1 -2
- package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
- package/client/components/bookmark-form/index.jsx +74 -14
- package/client/components/common/notification.jsx +34 -2
- package/client/components/common/notification.styl +18 -2
- package/client/components/main/wrapper.styl +0 -7
- package/client/components/rdp/rdp-session.jsx +44 -11
- package/client/components/session/session.jsx +13 -3
- package/client/components/setting-panel/deep-link-control.jsx +3 -2
- package/client/components/setting-panel/keywords-transport.jsx +0 -1
- package/client/components/shortcuts/shortcut-editor.jsx +12 -36
- package/client/components/shortcuts/shortcut-handler.js +11 -5
- package/client/components/sidebar/index.jsx +11 -1
- package/client/components/spice/spice-session.jsx +296 -0
- package/client/components/spice/spice.styl +4 -0
- package/client/components/tabs/add-btn-menu.jsx +9 -2
- package/client/components/terminal/attach-addon-custom.js +20 -76
- package/client/components/terminal/terminal.jsx +34 -28
- package/client/components/terminal/transfer-client-base.js +232 -0
- package/client/components/terminal/trzsz-client.js +306 -0
- package/client/components/terminal/xterm-loader.js +109 -0
- package/client/components/terminal/zmodem-client.js +13 -166
- package/client/components/text-editor/simple-editor.jsx +1 -2
- package/client/components/vnc/vnc-session.jsx +1 -1
- package/client/entry/electerm.jsx +0 -2
- package/client/store/load-data.js +1 -1
- package/client/store/store.js +1 -1
- package/client/store/system-menu.js +10 -0
- package/package.json +1 -1
- package/client/common/trzsz.js +0 -46
- package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
- package/client/components/common/notification-with-details.jsx +0 -34
- package/client/components/terminal/fs.js +0 -59
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react'
|
|
2
|
-
import { CloseOutlined } from '@ant-design/icons'
|
|
2
|
+
import { CloseOutlined, CopyOutlined } from '@ant-design/icons'
|
|
3
3
|
import classnames from 'classnames'
|
|
4
4
|
import generateId from '../../common/uid'
|
|
5
5
|
import { messageIcons } from '../../common/icon-helpers.jsx'
|
|
6
|
+
import { copy } from '../../common/clipboard'
|
|
6
7
|
import './notification.styl'
|
|
7
8
|
|
|
8
9
|
const notifications = []
|
|
@@ -69,6 +70,19 @@ export function NotificationContainer () {
|
|
|
69
70
|
)
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function getTextFromReactChildren (children) {
|
|
74
|
+
if (typeof children === 'string' || typeof children === 'number') {
|
|
75
|
+
return String(children)
|
|
76
|
+
}
|
|
77
|
+
if (React.isValidElement(children)) {
|
|
78
|
+
return getTextFromReactChildren(children.props.children)
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(children)) {
|
|
81
|
+
return children.map(getTextFromReactChildren).join('\n')
|
|
82
|
+
}
|
|
83
|
+
return ''
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
function NotificationItem ({ message, description, type, onClose, duration = 18.5 }) {
|
|
73
87
|
const timeoutRef = useRef(null)
|
|
74
88
|
|
|
@@ -97,6 +111,12 @@ function NotificationItem ({ message, description, type, onClose, duration = 18.
|
|
|
97
111
|
}
|
|
98
112
|
}
|
|
99
113
|
|
|
114
|
+
const handleCopy = (text, e) => {
|
|
115
|
+
e.stopPropagation()
|
|
116
|
+
const textToCopy = getTextFromReactChildren(text)
|
|
117
|
+
copy(textToCopy)
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
const className = classnames('notification', type)
|
|
101
121
|
|
|
102
122
|
return (
|
|
@@ -109,8 +129,20 @@ function NotificationItem ({ message, description, type, onClose, duration = 18.
|
|
|
109
129
|
<div className='notification-message'>
|
|
110
130
|
<div className='notification-icon'>{messageIcons[type]}</div>
|
|
111
131
|
<div className='notification-title' title={message}>{message}</div>
|
|
132
|
+
<CopyOutlined
|
|
133
|
+
className='notification-copy-icon'
|
|
134
|
+
onClick={(e) => handleCopy(message, e)}
|
|
135
|
+
/>
|
|
112
136
|
</div>
|
|
113
|
-
{description &&
|
|
137
|
+
{description && (
|
|
138
|
+
<div className='notification-description'>
|
|
139
|
+
{description}
|
|
140
|
+
<CopyOutlined
|
|
141
|
+
className='notification-copy-icon'
|
|
142
|
+
onClick={(e) => handleCopy(description, e)}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
114
146
|
</div>
|
|
115
147
|
<CloseOutlined className='notification-close' onClick={onClose} />
|
|
116
148
|
</div>
|
|
@@ -33,9 +33,10 @@
|
|
|
33
33
|
top 10px
|
|
34
34
|
|
|
35
35
|
.notification-description
|
|
36
|
-
word-wrap break-
|
|
36
|
+
word-wrap break-word
|
|
37
37
|
max-height 200px
|
|
38
38
|
overflow auto
|
|
39
|
+
padding 10px 0 0 0
|
|
39
40
|
|
|
40
41
|
.notification-close
|
|
41
42
|
position absolute
|
|
@@ -48,4 +49,19 @@
|
|
|
48
49
|
cursor pointer
|
|
49
50
|
padding 0
|
|
50
51
|
&:hover
|
|
51
|
-
color var(--text)
|
|
52
|
+
color var(--text)
|
|
53
|
+
|
|
54
|
+
.notification-copy-icon
|
|
55
|
+
cursor pointer
|
|
56
|
+
font-size 14px
|
|
57
|
+
position absolute
|
|
58
|
+
right 10px
|
|
59
|
+
top 45px
|
|
60
|
+
margin-left 8px
|
|
61
|
+
display none
|
|
62
|
+
.notification-message .notification-copy-icon
|
|
63
|
+
right 38px
|
|
64
|
+
top 14px
|
|
65
|
+
.notification-message:hover .notification-copy-icon,
|
|
66
|
+
.notification-description:hover .notification-copy-icon
|
|
67
|
+
display inline-block
|
|
@@ -6,14 +6,15 @@ import { handleErr } from '../../common/fetch'
|
|
|
6
6
|
import {
|
|
7
7
|
statusMap
|
|
8
8
|
} from '../../common/constants'
|
|
9
|
-
import {
|
|
10
|
-
Spin,
|
|
11
|
-
Select
|
|
12
|
-
} from 'antd'
|
|
13
9
|
import {
|
|
14
10
|
ReloadOutlined,
|
|
15
11
|
EditOutlined
|
|
16
12
|
} from '@ant-design/icons'
|
|
13
|
+
import {
|
|
14
|
+
Spin,
|
|
15
|
+
Select,
|
|
16
|
+
Switch
|
|
17
|
+
} from 'antd'
|
|
17
18
|
import * as ls from '../../common/safe-local-storage'
|
|
18
19
|
import scanCode from './code-scan'
|
|
19
20
|
import resolutions from './resolutions'
|
|
@@ -46,10 +47,13 @@ export default class RdpSession extends PureComponent {
|
|
|
46
47
|
constructor (props) {
|
|
47
48
|
const id = `rdp-reso-${props.tab.host}`
|
|
48
49
|
const resObj = ls.getItemJSON(id, resolutions[1])
|
|
50
|
+
const scaleViewportId = `rdp-scale-view-${props.tab.host}`
|
|
51
|
+
const scaleViewport = ls.getItemJSON(scaleViewportId, false)
|
|
49
52
|
super(props)
|
|
50
53
|
this.canvasRef = createRef()
|
|
51
54
|
this.state = {
|
|
52
55
|
loading: false,
|
|
56
|
+
scaleViewport,
|
|
53
57
|
...resObj
|
|
54
58
|
}
|
|
55
59
|
this.session = null
|
|
@@ -76,7 +80,7 @@ export default class RdpSession extends PureComponent {
|
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
runInitScript = () => {}
|
|
83
|
+
runInitScript = () => { }
|
|
80
84
|
|
|
81
85
|
setStatus = status => {
|
|
82
86
|
const id = this.props.tab?.id
|
|
@@ -267,7 +271,7 @@ export default class RdpSession extends PureComponent {
|
|
|
267
271
|
const kind = e.kind ? e.kind() : 'Unknown'
|
|
268
272
|
const bt = e.backtrace ? e.backtrace() : ''
|
|
269
273
|
return `[${kindNames[kind] || kind}] ${bt}`
|
|
270
|
-
} catch (_) {}
|
|
274
|
+
} catch (_) { }
|
|
271
275
|
}
|
|
272
276
|
return e?.message || e?.toString() || String(e)
|
|
273
277
|
}
|
|
@@ -446,6 +450,14 @@ export default class RdpSession extends PureComponent {
|
|
|
446
450
|
this.setState(res, this.handleReInit)
|
|
447
451
|
}
|
|
448
452
|
|
|
453
|
+
handleScaleViewChange = (v) => {
|
|
454
|
+
const scaleViewportId = `rdp-scale-view-${this.props.tab.host}`
|
|
455
|
+
ls.setItemJSON(scaleViewportId, v)
|
|
456
|
+
this.setState({
|
|
457
|
+
scaleViewport: v
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
|
|
449
461
|
renderHelp = () => {
|
|
450
462
|
return null
|
|
451
463
|
}
|
|
@@ -462,7 +474,7 @@ export default class RdpSession extends PureComponent {
|
|
|
462
474
|
onSendCtrlAltDel: this.handleSendCtrlAltDel,
|
|
463
475
|
screens: [], // RDP doesn't have multi-screen support like VNC
|
|
464
476
|
currentScreen: null,
|
|
465
|
-
onSelectScreen: () => {}, // No-op for RDP
|
|
477
|
+
onSelectScreen: () => { }, // No-op for RDP
|
|
466
478
|
fixedPosition,
|
|
467
479
|
showExitFullscreen,
|
|
468
480
|
className
|
|
@@ -517,8 +529,17 @@ export default class RdpSession extends PureComponent {
|
|
|
517
529
|
onChange: this.handleResChange,
|
|
518
530
|
popupMatchSelectWidth: false
|
|
519
531
|
}
|
|
532
|
+
const scaleProps = {
|
|
533
|
+
checked: this.state.scaleViewport,
|
|
534
|
+
onChange: this.handleScaleViewChange,
|
|
535
|
+
unCheckedChildren: window.translate('scaleViewport'),
|
|
536
|
+
checkedChildren: window.translate('scaleViewport'),
|
|
537
|
+
className: 'mg1l'
|
|
538
|
+
}
|
|
520
539
|
return (
|
|
521
|
-
<div
|
|
540
|
+
<div
|
|
541
|
+
className='pd1 fix session-v-info'
|
|
542
|
+
>
|
|
522
543
|
<div className='fleft'>
|
|
523
544
|
<ReloadOutlined
|
|
524
545
|
onClick={this.handleReInit}
|
|
@@ -547,6 +568,9 @@ export default class RdpSession extends PureComponent {
|
|
|
547
568
|
/>
|
|
548
569
|
{this.renderInfo()}
|
|
549
570
|
{this.renderHelp()}
|
|
571
|
+
<Switch
|
|
572
|
+
{...scaleProps}
|
|
573
|
+
/>
|
|
550
574
|
</div>
|
|
551
575
|
<div className='fright'>
|
|
552
576
|
{this.props.fullscreenIcon()}
|
|
@@ -582,25 +606,34 @@ export default class RdpSession extends PureComponent {
|
|
|
582
606
|
height: h + 'px'
|
|
583
607
|
}
|
|
584
608
|
}
|
|
585
|
-
const { width, height, loading } = this.state
|
|
609
|
+
const { width, height, loading, scaleViewport } = this.state
|
|
586
610
|
const canvasProps = {
|
|
587
611
|
width,
|
|
588
612
|
height,
|
|
589
613
|
tabIndex: 0
|
|
590
614
|
}
|
|
615
|
+
if (scaleViewport) {
|
|
616
|
+
Object.assign(canvasProps, {
|
|
617
|
+
style: {
|
|
618
|
+
width: '100%',
|
|
619
|
+
objectFit: 'contain'
|
|
620
|
+
}
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
const cls = 'rdp-session-wrap session-v-wrap'
|
|
591
624
|
const controlProps = this.getControlProps()
|
|
592
625
|
return (
|
|
593
626
|
<Spin spinning={loading}>
|
|
594
627
|
<div
|
|
595
628
|
{...rdpProps}
|
|
596
|
-
className=
|
|
629
|
+
className={cls}
|
|
597
630
|
>
|
|
598
631
|
{this.renderControl()}
|
|
599
|
-
<RemoteFloatControl {...controlProps} />
|
|
600
632
|
<canvas
|
|
601
633
|
{...canvasProps}
|
|
602
634
|
ref={this.canvasRef}
|
|
603
635
|
/>
|
|
636
|
+
<RemoteFloatControl {...controlProps} />
|
|
604
637
|
</div>
|
|
605
638
|
</Spin>
|
|
606
639
|
)
|
|
@@ -8,6 +8,7 @@ import Sftp from '../sftp/sftp-entry'
|
|
|
8
8
|
import RdpSession from '../rdp/rdp-session'
|
|
9
9
|
import VncSession from '../vnc/vnc-session'
|
|
10
10
|
import WebSession from '../web/web-session.jsx'
|
|
11
|
+
import SpiceSession from '../spice/spice-session'
|
|
11
12
|
import {
|
|
12
13
|
SearchOutlined,
|
|
13
14
|
FullscreenOutlined,
|
|
@@ -29,7 +30,8 @@ import {
|
|
|
29
30
|
terminalVncType,
|
|
30
31
|
terminalWebType,
|
|
31
32
|
terminalTelnetType,
|
|
32
|
-
terminalFtpType
|
|
33
|
+
terminalFtpType,
|
|
34
|
+
terminalSpiceType
|
|
33
35
|
} from '../../common/constants'
|
|
34
36
|
import { SplitViewIcon } from '../icons/split-view'
|
|
35
37
|
import { refs } from '../common/ref'
|
|
@@ -269,7 +271,7 @@ export default class SessionWrapper extends Component {
|
|
|
269
271
|
/>
|
|
270
272
|
)
|
|
271
273
|
}
|
|
272
|
-
if (type === terminalRdpType || type === terminalVncType) {
|
|
274
|
+
if (type === terminalRdpType || type === terminalVncType || type === terminalSpiceType) {
|
|
273
275
|
const rdpProps = {
|
|
274
276
|
tab: this.props.tab,
|
|
275
277
|
...pick(this.props, [
|
|
@@ -299,6 +301,13 @@ export default class SessionWrapper extends Component {
|
|
|
299
301
|
/>
|
|
300
302
|
)
|
|
301
303
|
}
|
|
304
|
+
if (type === terminalSpiceType) {
|
|
305
|
+
return (
|
|
306
|
+
<SpiceSession
|
|
307
|
+
{...rdpProps}
|
|
308
|
+
/>
|
|
309
|
+
)
|
|
310
|
+
}
|
|
302
311
|
|
|
303
312
|
return (
|
|
304
313
|
<RdpSession
|
|
@@ -373,7 +382,8 @@ export default class SessionWrapper extends Component {
|
|
|
373
382
|
type === terminalVncType ||
|
|
374
383
|
type === terminalWebType ||
|
|
375
384
|
type === terminalTelnetType ||
|
|
376
|
-
type === terminalFtpType
|
|
385
|
+
type === terminalFtpType ||
|
|
386
|
+
type === terminalSpiceType
|
|
377
387
|
}
|
|
378
388
|
|
|
379
389
|
calcSftpWidthHeight = () => {
|
|
@@ -65,12 +65,13 @@ export default function DeepLinkControl () {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
const renderTooltipContent = () => {
|
|
68
|
-
const protocols = ['ssh', 'telnet']
|
|
68
|
+
const protocols = ['ssh', 'telnet', 'rdp', 'vnc', 'serial', 'spice', 'electerm']
|
|
69
|
+
const tip = `Register electerm to handle protocol URLs (${protocols.join('://, ')})`
|
|
69
70
|
|
|
70
71
|
return (
|
|
71
72
|
<div>
|
|
72
73
|
<div className='pd1b'>
|
|
73
|
-
|
|
74
|
+
{tip}
|
|
74
75
|
</div>
|
|
75
76
|
|
|
76
77
|
{registrationStatus && (
|
|
@@ -27,7 +27,6 @@ export default class KeywordsTransport extends BookmarkTransport {
|
|
|
27
27
|
const { store } = this.props
|
|
28
28
|
const arr = store.config.keywords || []
|
|
29
29
|
const txt = JSON.stringify(arr, null, 2)
|
|
30
|
-
console.log(txt, 'txt')
|
|
31
30
|
const stamp = time(undefined, 'YYYY-MM-DD-HH-mm-ss')
|
|
32
31
|
download('electerm-' + this.name + '-' + stamp + '.json', txt)
|
|
33
32
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PureComponent } from 'react'
|
|
1
|
+
import { PureComponent, createRef } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
Button,
|
|
4
4
|
Input
|
|
@@ -19,39 +19,27 @@ export default class ShortcutEdit extends PureComponent {
|
|
|
19
19
|
data: null
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
containerRef = createRef()
|
|
23
|
+
|
|
24
|
+
componentWillUnmount () {
|
|
25
|
+
this.removeEventListener()
|
|
26
|
+
}
|
|
27
|
+
|
|
22
28
|
addEventListener = () => {
|
|
23
|
-
|
|
24
|
-
elem?.addEventListener('click', this.handleClickOuter)
|
|
29
|
+
document.addEventListener('click', this.handleClickOuter, true)
|
|
25
30
|
document.addEventListener('keydown', this.handleKeyDown)
|
|
26
31
|
document.addEventListener('mousewheel', this.handleKeyDown)
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
removeEventListener = () => {
|
|
30
|
-
|
|
31
|
-
elem?.removeEventListener('click', this.handleClickOuter)
|
|
35
|
+
document.removeEventListener('click', this.handleClickOuter, true)
|
|
32
36
|
document.removeEventListener('keydown', this.handleKeyDown)
|
|
33
37
|
document.removeEventListener('mousewheel', this.handleKeyDown)
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
isInsideElement = (event) => {
|
|
37
|
-
const { target } = event
|
|
38
|
-
const cls = this.getCls()
|
|
39
|
-
if (!target || !target.classList) {
|
|
40
|
-
return false
|
|
41
|
-
} else if (target.classList.contains(cls)) {
|
|
42
|
-
return true
|
|
43
|
-
} else {
|
|
44
|
-
const parent = target.parentElement
|
|
45
|
-
if (parent !== null) {
|
|
46
|
-
return this.isInsideElement({ target: parent })
|
|
47
|
-
} else {
|
|
48
|
-
return false
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
40
|
handleClickOuter = (e) => {
|
|
54
|
-
|
|
41
|
+
const container = this.containerRef.current
|
|
42
|
+
if (container && !container.contains(e.target)) {
|
|
55
43
|
this.handleCancel()
|
|
56
44
|
}
|
|
57
45
|
}
|
|
@@ -78,11 +66,6 @@ export default class ShortcutEdit extends PureComponent {
|
|
|
78
66
|
this.handleCancel()
|
|
79
67
|
}
|
|
80
68
|
|
|
81
|
-
getCls = () => {
|
|
82
|
-
const { index } = this.props.data
|
|
83
|
-
return 'shortcut-control-' + index
|
|
84
|
-
}
|
|
85
|
-
|
|
86
69
|
warnCtrolKey = throttle(() => {
|
|
87
70
|
message.info(
|
|
88
71
|
'Must have one of Ctrl or Shift or Alt or Meta key',
|
|
@@ -131,13 +114,6 @@ export default class ShortcutEdit extends PureComponent {
|
|
|
131
114
|
})
|
|
132
115
|
}
|
|
133
116
|
|
|
134
|
-
handleClickOutside = () => {
|
|
135
|
-
this.removeEventListener()
|
|
136
|
-
this.setState({
|
|
137
|
-
editMode: false
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
117
|
renderStatic () {
|
|
142
118
|
const {
|
|
143
119
|
shortcut
|
|
@@ -199,7 +175,7 @@ export default class ShortcutEdit extends PureComponent {
|
|
|
199
175
|
return this.renderStatic()
|
|
200
176
|
}
|
|
201
177
|
return (
|
|
202
|
-
<div
|
|
178
|
+
<div ref={this.containerRef}>
|
|
203
179
|
<Input
|
|
204
180
|
suffix={this.renderAfter()}
|
|
205
181
|
value={shortcut}
|
|
@@ -175,12 +175,18 @@ export function shortcutExtend (Cls) {
|
|
|
175
175
|
type === 'keydown' &&
|
|
176
176
|
!altKey &&
|
|
177
177
|
!shiftKey &&
|
|
178
|
-
ctrlKey
|
|
179
|
-
this.zmodemClient &&
|
|
180
|
-
this.zmodemClient.isActive
|
|
178
|
+
ctrlKey
|
|
181
179
|
) {
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
// Cancel zmodem transfer if active
|
|
181
|
+
if (this.zmodemClient && this.zmodemClient.isActive) {
|
|
182
|
+
this.zmodemClient.cancel()
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
// Cancel trzsz transfer if active
|
|
186
|
+
if (this.trzszClient && this.trzszClient.isActive) {
|
|
187
|
+
this.trzszClient.cancel()
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
let codeName
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
SettingOutlined,
|
|
8
8
|
UpCircleOutlined,
|
|
9
9
|
BarsOutlined,
|
|
10
|
-
AppstoreOutlined
|
|
10
|
+
AppstoreOutlined,
|
|
11
|
+
RobotOutlined
|
|
11
12
|
} from '@ant-design/icons'
|
|
12
13
|
import { Tooltip } from 'antd'
|
|
13
14
|
import SideBarPanel from './sidebar-panel'
|
|
@@ -86,6 +87,7 @@ export default function Sidebar (props) {
|
|
|
86
87
|
|
|
87
88
|
const {
|
|
88
89
|
onNewSsh,
|
|
90
|
+
onNewSshAI,
|
|
89
91
|
openSetting,
|
|
90
92
|
openAbout,
|
|
91
93
|
openSettingSync,
|
|
@@ -142,6 +144,14 @@ export default function Sidebar (props) {
|
|
|
142
144
|
onClick={onNewSsh}
|
|
143
145
|
/>
|
|
144
146
|
</SideIcon>
|
|
147
|
+
<SideIcon
|
|
148
|
+
title={e('createBookmarkByAI')}
|
|
149
|
+
>
|
|
150
|
+
<RobotOutlined
|
|
151
|
+
className='font20 iblock control-icon'
|
|
152
|
+
onClick={onNewSshAI}
|
|
153
|
+
/>
|
|
154
|
+
</SideIcon>
|
|
145
155
|
<SideIcon
|
|
146
156
|
title={e(settingMap.bookmarks)}
|
|
147
157
|
active={bookmarksActive}
|