@electerm/electerm-react 2.3.151 → 2.3.176
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 +4 -2
- package/client/common/db.js +2 -1
- package/client/common/download.jsx +1 -1
- package/client/common/error-handler.jsx +1 -1
- package/client/common/fetch.jsx +1 -1
- package/client/common/init-setting-item.js +7 -0
- package/client/components/common/modal.jsx +89 -0
- package/client/components/common/modal.styl +77 -0
- package/client/components/common/notification-with-details.jsx +34 -0
- package/client/components/file-transfer/conflict-resolve.jsx +2 -1
- package/client/components/file-transfer/transfer-speed-format.js +6 -0
- package/client/components/file-transfer/transfer.jsx +5 -2
- package/client/components/file-transfer/transports-action-store.jsx +14 -1
- package/client/components/main/connection-hopping-warnning.jsx +1 -1
- package/client/components/main/main.jsx +2 -0
- package/client/components/quick-commands/qm.styl +0 -10
- package/client/components/quick-commands/quick-command-item.jsx +2 -5
- package/client/components/quick-commands/quick-commands-box.jsx +12 -23
- package/client/components/setting-panel/setting-common.jsx +4 -3
- package/client/components/setting-panel/setting-modal.jsx +2 -1
- package/client/components/setting-panel/start-session-select.jsx +146 -21
- package/client/components/setting-panel/text-bg-modal.jsx +15 -4
- package/client/components/setting-sync/setting-sync-form.jsx +1 -1
- package/client/components/sftp/file-info-modal.jsx +2 -1
- package/client/components/sftp/file-item.jsx +2 -0
- package/client/components/sftp/sftp-entry.jsx +1 -1
- package/client/components/sftp/sftp.styl +1 -1
- package/client/components/sidebar/info-modal.jsx +53 -34
- package/client/components/sidebar/info.styl +0 -7
- package/client/components/ssh-config/ssh-config-load-notify.jsx +1 -1
- package/client/components/tabs/index.jsx +6 -58
- package/client/components/tabs/layout-menu.jsx +75 -0
- package/client/components/tabs/layout-select.jsx +60 -0
- package/client/components/tabs/tabs.styl +64 -0
- package/client/components/tabs/workspace-save-modal.jsx +117 -0
- package/client/components/tabs/workspace-select.jsx +79 -0
- package/client/components/terminal/attach-addon-custom.js +7 -1
- package/client/components/terminal/terminal-interactive.jsx +2 -1
- package/client/components/terminal/terminal.jsx +1 -2
- package/client/components/text-editor/text-editor.jsx +2 -1
- package/client/components/tree-list/move-item-modal.jsx +2 -1
- package/client/components/vnc/vnc-session.jsx +2 -2
- package/client/components/widgets/widget-control.jsx +12 -6
- package/client/components/widgets/widget-form.jsx +16 -18
- package/client/components/widgets/widget-instance.jsx +44 -9
- package/client/components/widgets/widget-notification-with-details.jsx +34 -0
- package/client/css/basic.styl +3 -1
- package/client/css/includes/box.styl +2 -2
- package/client/store/common.js +9 -5
- package/client/store/init-state.js +4 -0
- package/client/store/load-data.js +15 -6
- package/client/store/mcp-handler.js +640 -0
- package/client/store/store.js +4 -0
- package/client/store/widgets.js +4 -0
- package/client/store/workspace.js +108 -0
- package/package.json +1 -1
|
@@ -244,3 +244,67 @@
|
|
|
244
244
|
max-height 300px
|
|
245
245
|
overflow-y auto
|
|
246
246
|
|
|
247
|
+
// Layout and Workspace dropdown styles
|
|
248
|
+
.layout-workspace-dropdown
|
|
249
|
+
background var(--main)
|
|
250
|
+
border-radius 4px
|
|
251
|
+
box-shadow 0 2px 8px rgba(0, 0, 0, 0.15)
|
|
252
|
+
min-width 200px
|
|
253
|
+
padding 8px
|
|
254
|
+
.ant-tabs-nav
|
|
255
|
+
margin-bottom 8px
|
|
256
|
+
|
|
257
|
+
.layout-menu-content
|
|
258
|
+
max-height 300px
|
|
259
|
+
overflow-y auto
|
|
260
|
+
|
|
261
|
+
.layout-menu-item
|
|
262
|
+
padding 6px 12px
|
|
263
|
+
cursor pointer
|
|
264
|
+
border-radius 4px
|
|
265
|
+
display flex
|
|
266
|
+
align-items center
|
|
267
|
+
gap 8px
|
|
268
|
+
color var(--text)
|
|
269
|
+
&:hover
|
|
270
|
+
background var(--main-dark)
|
|
271
|
+
&.active
|
|
272
|
+
background var(--primary)
|
|
273
|
+
color #fff
|
|
274
|
+
|
|
275
|
+
.workspace-menu-content
|
|
276
|
+
max-height 300px
|
|
277
|
+
overflow-y auto
|
|
278
|
+
|
|
279
|
+
.workspace-save-btn
|
|
280
|
+
margin-bottom 8px
|
|
281
|
+
|
|
282
|
+
.workspace-list
|
|
283
|
+
display flex
|
|
284
|
+
flex-direction column
|
|
285
|
+
gap 4px
|
|
286
|
+
|
|
287
|
+
.workspace-item
|
|
288
|
+
padding 6px 12px
|
|
289
|
+
cursor pointer
|
|
290
|
+
border-radius 4px
|
|
291
|
+
display flex
|
|
292
|
+
align-items center
|
|
293
|
+
justify-content space-between
|
|
294
|
+
color var(--text)
|
|
295
|
+
&:hover
|
|
296
|
+
background var(--main-dark)
|
|
297
|
+
.workspace-delete-icon
|
|
298
|
+
opacity 0
|
|
299
|
+
color var(--text-dark)
|
|
300
|
+
&:hover
|
|
301
|
+
color var(--error)
|
|
302
|
+
&:hover .workspace-delete-icon
|
|
303
|
+
opacity 1
|
|
304
|
+
|
|
305
|
+
.workspace-name
|
|
306
|
+
flex 1
|
|
307
|
+
overflow hidden
|
|
308
|
+
text-overflow ellipsis
|
|
309
|
+
white-space nowrap
|
|
310
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace save modal component - standalone modal
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, { useState } from 'react'
|
|
6
|
+
import { auto } from 'manate/react'
|
|
7
|
+
import Modal from '../common/modal'
|
|
8
|
+
import { Input, Select, Button, Space, message, Radio } from 'antd'
|
|
9
|
+
import { SaveOutlined, EditOutlined } from '@ant-design/icons'
|
|
10
|
+
|
|
11
|
+
const e = window.translate
|
|
12
|
+
|
|
13
|
+
export default auto(function WorkspaceSaveModal ({ store }) {
|
|
14
|
+
const { workspaceSaveModalVisible, workspaces } = store
|
|
15
|
+
const [name, setName] = useState('')
|
|
16
|
+
const [selectedId, setSelectedId] = useState(null)
|
|
17
|
+
const [saveMode, setSaveMode] = useState('new') // 'new' or 'overwrite'
|
|
18
|
+
|
|
19
|
+
if (!workspaceSaveModalVisible) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function handleClose () {
|
|
24
|
+
window.store.workspaceSaveModalVisible = false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleSave () {
|
|
28
|
+
if (saveMode === 'new') {
|
|
29
|
+
if (!name.trim()) {
|
|
30
|
+
message.error(e('name needed'))
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
window.store.saveWorkspace(name.trim())
|
|
34
|
+
message.success(e('saved'))
|
|
35
|
+
} else {
|
|
36
|
+
if (!selectedId) {
|
|
37
|
+
message.error('please Select Workspace')
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
const ws = workspaces.find(w => w.id === selectedId)
|
|
41
|
+
window.store.saveWorkspace(ws?.name || name, selectedId)
|
|
42
|
+
message.success(e('saved'))
|
|
43
|
+
}
|
|
44
|
+
setName('')
|
|
45
|
+
setSelectedId(null)
|
|
46
|
+
setSaveMode('new')
|
|
47
|
+
handleClose()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function handleCancel () {
|
|
51
|
+
setName('')
|
|
52
|
+
setSelectedId(null)
|
|
53
|
+
setSaveMode('new')
|
|
54
|
+
handleClose()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const options = workspaces.map(w => ({
|
|
58
|
+
label: w.name,
|
|
59
|
+
value: w.id
|
|
60
|
+
}))
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Modal
|
|
64
|
+
title={e('save')}
|
|
65
|
+
open={workspaceSaveModalVisible}
|
|
66
|
+
onCancel={handleCancel}
|
|
67
|
+
footer={null}
|
|
68
|
+
width={400}
|
|
69
|
+
>
|
|
70
|
+
<div className='pd1y'>
|
|
71
|
+
<Space direction='vertical' block>
|
|
72
|
+
<Radio.Group
|
|
73
|
+
value={saveMode}
|
|
74
|
+
onChange={ev => setSaveMode(ev.target.value)}
|
|
75
|
+
>
|
|
76
|
+
<Radio value='new'>
|
|
77
|
+
<SaveOutlined className='mg1r' />
|
|
78
|
+
{e('saveAsNew')}
|
|
79
|
+
</Radio>
|
|
80
|
+
<Radio value='overwrite' disabled={!workspaces.length}>
|
|
81
|
+
<EditOutlined className='mg1r' />
|
|
82
|
+
{e('overwrite')}
|
|
83
|
+
</Radio>
|
|
84
|
+
</Radio.Group>
|
|
85
|
+
|
|
86
|
+
{saveMode === 'new'
|
|
87
|
+
? (
|
|
88
|
+
<Input
|
|
89
|
+
placeholder={e('name')}
|
|
90
|
+
value={name}
|
|
91
|
+
onChange={e => setName(e.target.value)}
|
|
92
|
+
onPressEnter={handleSave}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
95
|
+
: (
|
|
96
|
+
<Select
|
|
97
|
+
placeholder={e('workspaces')}
|
|
98
|
+
value={selectedId}
|
|
99
|
+
onChange={setSelectedId}
|
|
100
|
+
options={options}
|
|
101
|
+
style={{ width: '100%' }}
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
|
|
105
|
+
<div className='pd1t'>
|
|
106
|
+
<Button type='primary' onClick={handleSave}>
|
|
107
|
+
{e('save')}
|
|
108
|
+
</Button>
|
|
109
|
+
<Button className='mg1l' onClick={handleCancel}>
|
|
110
|
+
{e('cancel')}
|
|
111
|
+
</Button>
|
|
112
|
+
</div>
|
|
113
|
+
</Space>
|
|
114
|
+
</div>
|
|
115
|
+
</Modal>
|
|
116
|
+
)
|
|
117
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace select content component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import { Button, Empty, Popconfirm } from 'antd'
|
|
7
|
+
import {
|
|
8
|
+
SaveOutlined,
|
|
9
|
+
DeleteOutlined
|
|
10
|
+
} from '@ant-design/icons'
|
|
11
|
+
import { auto } from 'manate/react'
|
|
12
|
+
|
|
13
|
+
const e = window.translate
|
|
14
|
+
|
|
15
|
+
export default auto(function WorkspaceSelect (props) {
|
|
16
|
+
const { store } = props
|
|
17
|
+
const { workspaces } = store
|
|
18
|
+
|
|
19
|
+
function handleLoadWorkspace (id) {
|
|
20
|
+
window.store.loadWorkspace(id)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function handleDeleteWorkspace (id, ev) {
|
|
24
|
+
ev.stopPropagation()
|
|
25
|
+
window.store.deleteWorkspace(id)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleSaveClick () {
|
|
29
|
+
window.store.workspaceSaveModalVisible = true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className='workspace-menu-content'>
|
|
34
|
+
<div className='workspace-save-btn pd1b'>
|
|
35
|
+
<Button
|
|
36
|
+
type='primary'
|
|
37
|
+
icon={<SaveOutlined />}
|
|
38
|
+
size='small'
|
|
39
|
+
onClick={handleSaveClick}
|
|
40
|
+
block
|
|
41
|
+
>
|
|
42
|
+
{e('save')}
|
|
43
|
+
</Button>
|
|
44
|
+
</div>
|
|
45
|
+
{workspaces.length === 0
|
|
46
|
+
? (
|
|
47
|
+
<Empty
|
|
48
|
+
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
49
|
+
description='No items'
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
: (
|
|
53
|
+
<div className='workspace-list'>
|
|
54
|
+
{workspaces.map(ws => (
|
|
55
|
+
<div
|
|
56
|
+
key={ws.id}
|
|
57
|
+
className='workspace-item'
|
|
58
|
+
onClick={() => handleLoadWorkspace(ws.id)}
|
|
59
|
+
>
|
|
60
|
+
<span className='workspace-name'>{ws.name}</span>
|
|
61
|
+
<Popconfirm
|
|
62
|
+
title={e('del') + '?'}
|
|
63
|
+
onConfirm={(ev) => handleDeleteWorkspace(ws.id, ev)}
|
|
64
|
+
onCancel={(ev) => ev.stopPropagation()}
|
|
65
|
+
okText={e('ok')}
|
|
66
|
+
cancelText={e('cancel')}
|
|
67
|
+
>
|
|
68
|
+
<DeleteOutlined
|
|
69
|
+
className='workspace-delete-icon'
|
|
70
|
+
onClick={(ev) => ev.stopPropagation()}
|
|
71
|
+
/>
|
|
72
|
+
</Popconfirm>
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
})
|
|
@@ -34,7 +34,13 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
onMsg = (ev) => {
|
|
37
|
-
|
|
37
|
+
// When in alternate screen mode (like vim, less, or TUI apps like Claude Code),
|
|
38
|
+
// bypass trzsz processing to avoid interference with the application's display
|
|
39
|
+
if (this.term?.buffer?.active?.type === 'alternate') {
|
|
40
|
+
this.writeToTerminal(ev.data)
|
|
41
|
+
} else {
|
|
42
|
+
this.trzsz.processServerOutput(ev.data)
|
|
43
|
+
}
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
writeToTerminal = (data) => {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useEffect, useState } from 'react'
|
|
6
|
-
import {
|
|
6
|
+
import { Form, Button } from 'antd'
|
|
7
|
+
import Modal from '../common/modal'
|
|
7
8
|
import InputAutoFocus from '../common/input-auto-focus'
|
|
8
9
|
import wait from '../../common/wait'
|
|
9
10
|
|
|
@@ -178,7 +178,6 @@ class Term extends Component {
|
|
|
178
178
|
clearTimeout(this.timers[k])
|
|
179
179
|
this.timers[k] = null
|
|
180
180
|
})
|
|
181
|
-
this.timers = null
|
|
182
181
|
this.onClose = true
|
|
183
182
|
if (this.socket) {
|
|
184
183
|
this.socket.close()
|
|
@@ -1321,7 +1320,7 @@ class Term extends Component {
|
|
|
1321
1320
|
}
|
|
1322
1321
|
this.socketCloseWarning = notification.warning({
|
|
1323
1322
|
key,
|
|
1324
|
-
|
|
1323
|
+
title: e('socketCloseTip'),
|
|
1325
1324
|
duration: 30,
|
|
1326
1325
|
description: (
|
|
1327
1326
|
<div className='pd2y'>
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { PureComponent } from 'react'
|
|
6
6
|
import TextEditorForm from './text-editor-form'
|
|
7
|
-
import { Spin
|
|
7
|
+
import { Spin } from 'antd'
|
|
8
|
+
import Modal from '../common/modal'
|
|
8
9
|
import resolve from '../../common/resolve'
|
|
9
10
|
import { refsStatic, refs } from '../common/ref'
|
|
10
11
|
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
SearchOutlined
|
|
6
6
|
} from '@ant-design/icons'
|
|
7
7
|
import buildGroupData from '../bookmark-form/common/bookmark-group-tree-format'
|
|
8
|
-
import { Tree,
|
|
8
|
+
import { Tree, Button, Input } from 'antd'
|
|
9
|
+
import Modal from '../common/modal'
|
|
9
10
|
import { auto } from 'manate/react'
|
|
10
11
|
const e = window.translate
|
|
11
12
|
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
Spin,
|
|
12
12
|
message,
|
|
13
|
-
Modal,
|
|
14
13
|
Tag
|
|
15
14
|
} from 'antd'
|
|
15
|
+
import Modal from '../common/modal'
|
|
16
16
|
import * as ls from '../../common/safe-local-storage'
|
|
17
17
|
import { copy } from '../../common/clipboard'
|
|
18
18
|
import resolutions from '../rdp/resolutions'
|
|
@@ -250,7 +250,7 @@ export default class VncSession extends RdpSession {
|
|
|
250
250
|
title: e('credentialsRequired'),
|
|
251
251
|
content: this.renderForm(['password']),
|
|
252
252
|
footer: null,
|
|
253
|
-
|
|
253
|
+
open: true
|
|
254
254
|
}
|
|
255
255
|
return (
|
|
256
256
|
<Modal
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import React, { useState } from 'react'
|
|
5
5
|
import WidgetForm from './widget-form'
|
|
6
|
-
import {
|
|
7
|
-
message
|
|
8
|
-
} from 'antd'
|
|
6
|
+
import { showMsg } from './widget-notification-with-details'
|
|
9
7
|
|
|
10
|
-
export default function WidgetControl ({ formData }) {
|
|
8
|
+
export default function WidgetControl ({ formData, widgetInstancesLength }) {
|
|
11
9
|
const [loading, setLoading] = useState(false)
|
|
12
10
|
const widget = formData
|
|
13
11
|
if (!widget.id) {
|
|
@@ -18,6 +16,12 @@ export default function WidgetControl ({ formData }) {
|
|
|
18
16
|
)
|
|
19
17
|
}
|
|
20
18
|
|
|
19
|
+
// Check if this widget already has a running instance
|
|
20
|
+
// widgetInstancesLength is used to trigger re-render when instances change
|
|
21
|
+
const hasRunningInstance = widgetInstancesLength > 0 && window.store.widgetInstances.some(
|
|
22
|
+
instance => instance.widgetId === widget.id
|
|
23
|
+
)
|
|
24
|
+
|
|
21
25
|
const handleFormSubmit = async (config) => {
|
|
22
26
|
setLoading(true)
|
|
23
27
|
try {
|
|
@@ -30,9 +34,9 @@ export default function WidgetControl ({ formData }) {
|
|
|
30
34
|
} = result
|
|
31
35
|
if (!instanceId) {
|
|
32
36
|
if (success === false) {
|
|
33
|
-
|
|
37
|
+
showMsg('Failed to run widget', 'error', null, 10, error || '')
|
|
34
38
|
} else {
|
|
35
|
-
|
|
39
|
+
showMsg(msg, 'success', null, 10)
|
|
36
40
|
}
|
|
37
41
|
return
|
|
38
42
|
}
|
|
@@ -45,6 +49,7 @@ export default function WidgetControl ({ formData }) {
|
|
|
45
49
|
config
|
|
46
50
|
}
|
|
47
51
|
window.store.widgetInstances.push(instance)
|
|
52
|
+
showMsg(msg, 'success', result.serverInfo, 10)
|
|
48
53
|
} catch (err) {
|
|
49
54
|
console.error('Failed to run widget:', err)
|
|
50
55
|
} finally {
|
|
@@ -58,6 +63,7 @@ export default function WidgetControl ({ formData }) {
|
|
|
58
63
|
widget={widget}
|
|
59
64
|
onSubmit={handleFormSubmit}
|
|
60
65
|
loading={loading}
|
|
66
|
+
hasRunningInstance={hasRunningInstance}
|
|
61
67
|
/>
|
|
62
68
|
</div>
|
|
63
69
|
)
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Widget form component
|
|
3
3
|
*/
|
|
4
4
|
import React from 'react'
|
|
5
|
-
import { Form, Input, InputNumber, Switch, Select, Button,
|
|
5
|
+
import { Form, Input, InputNumber, Switch, Select, Button, Tooltip } from 'antd'
|
|
6
6
|
import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
|
|
7
7
|
|
|
8
|
-
export default function WidgetForm ({ widget, onSubmit, loading }) {
|
|
8
|
+
export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInstance }) {
|
|
9
9
|
const [form] = Form.useForm()
|
|
10
10
|
|
|
11
11
|
if (!widget) {
|
|
@@ -13,17 +13,13 @@ export default function WidgetForm ({ widget, onSubmit, loading }) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const { info } = widget
|
|
16
|
-
const { configs, type } = info
|
|
16
|
+
const { configs, type, singleInstance } = info
|
|
17
17
|
const isInstanceWidget = type === 'instance'
|
|
18
18
|
const txt = isInstanceWidget ? 'Start widget' : 'Run widget'
|
|
19
|
+
const isDisabled = loading || (singleInstance && hasRunningInstance)
|
|
19
20
|
|
|
20
21
|
const handleSubmit = async (values) => {
|
|
21
|
-
|
|
22
|
-
await onSubmit(values)
|
|
23
|
-
message.success('Widget started successfully')
|
|
24
|
-
} catch (error) {
|
|
25
|
-
message.error('Failed to start widget: ' + error.message)
|
|
26
|
-
}
|
|
22
|
+
onSubmit(values)
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
const renderFormItem = (config) => {
|
|
@@ -86,7 +82,7 @@ export default function WidgetForm ({ widget, onSubmit, loading }) {
|
|
|
86
82
|
|
|
87
83
|
return (
|
|
88
84
|
<div className='widget-form'>
|
|
89
|
-
<div className='pd1b'>
|
|
85
|
+
<div className='pd1b alignright'>
|
|
90
86
|
<h4>{info.name}</h4>
|
|
91
87
|
<p>{info.description}</p>
|
|
92
88
|
</div>
|
|
@@ -100,14 +96,16 @@ export default function WidgetForm ({ widget, onSubmit, loading }) {
|
|
|
100
96
|
<Form.Item
|
|
101
97
|
{...tailFormItemLayout}
|
|
102
98
|
>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
99
|
+
<Tooltip title={isDisabled && singleInstance && hasRunningInstance ? 'Already running, only one instance allowed' : ''}>
|
|
100
|
+
<Button
|
|
101
|
+
type='primary'
|
|
102
|
+
htmlType='submit'
|
|
103
|
+
loading={loading}
|
|
104
|
+
disabled={isDisabled}
|
|
105
|
+
>
|
|
106
|
+
{txt}
|
|
107
|
+
</Button>
|
|
108
|
+
</Tooltip>
|
|
111
109
|
</Form.Item>
|
|
112
110
|
</Form>
|
|
113
111
|
</div>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Popconfirm } from 'antd'
|
|
2
|
-
import { CloseOutlined } from '@ant-design/icons'
|
|
1
|
+
import { Popconfirm, Popover } from 'antd'
|
|
2
|
+
import { CloseOutlined, CopyOutlined } from '@ant-design/icons'
|
|
3
|
+
import { copy } from '../../common/clipboard'
|
|
3
4
|
|
|
4
5
|
const e = window.translate
|
|
5
6
|
|
|
6
7
|
export default function WidgetInstance ({ item }) {
|
|
7
|
-
const { id, title } = item
|
|
8
|
+
const { id, title, serverInfo } = item
|
|
8
9
|
const cls = 'item-list-unit'
|
|
9
10
|
const delProps = {
|
|
10
11
|
title: e('del'),
|
|
@@ -25,17 +26,51 @@ export default function WidgetInstance ({ item }) {
|
|
|
25
26
|
cancelText: e('cancel'),
|
|
26
27
|
placement: 'top'
|
|
27
28
|
}
|
|
29
|
+
const handleCopy = () => {
|
|
30
|
+
if (serverInfo && serverInfo.url) {
|
|
31
|
+
copy(serverInfo.url)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const popoverContent = serverInfo
|
|
35
|
+
? (
|
|
36
|
+
<div>
|
|
37
|
+
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
38
|
+
<span>URL: {serverInfo.url}</span>
|
|
39
|
+
<CopyOutlined
|
|
40
|
+
className='pointer mg1l'
|
|
41
|
+
onClick={handleCopy}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div>Path: {serverInfo.path}</div>
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
: null
|
|
48
|
+
const titleDiv = (
|
|
49
|
+
<div
|
|
50
|
+
title={title}
|
|
51
|
+
className='elli pd1y pd2x list-item-title'
|
|
52
|
+
>
|
|
53
|
+
{title}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
28
56
|
return (
|
|
29
57
|
<div
|
|
30
58
|
key={id}
|
|
31
59
|
className={cls}
|
|
32
60
|
>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
61
|
+
{
|
|
62
|
+
serverInfo
|
|
63
|
+
? (
|
|
64
|
+
<Popover
|
|
65
|
+
content={popoverContent}
|
|
66
|
+
trigger='hover'
|
|
67
|
+
placement='top'
|
|
68
|
+
>
|
|
69
|
+
{titleDiv}
|
|
70
|
+
</Popover>
|
|
71
|
+
)
|
|
72
|
+
: titleDiv
|
|
73
|
+
}
|
|
39
74
|
<Popconfirm
|
|
40
75
|
{...popProps}
|
|
41
76
|
>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { notification } from 'antd'
|
|
2
|
+
import { CopyOutlined } from '@ant-design/icons'
|
|
3
|
+
import { copy } from '../../common/clipboard'
|
|
4
|
+
|
|
5
|
+
export function showMsg (message, type = 'success', serverInfo = null, duration = 10, description = '') {
|
|
6
|
+
const handleCopy = () => {
|
|
7
|
+
if (serverInfo && serverInfo.url) {
|
|
8
|
+
copy(serverInfo.url)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let desc = description
|
|
13
|
+
if (serverInfo) {
|
|
14
|
+
desc = (
|
|
15
|
+
<div>
|
|
16
|
+
{description && <div>{description}</div>}
|
|
17
|
+
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
18
|
+
<span>URL: {serverInfo.url}</span>
|
|
19
|
+
<CopyOutlined
|
|
20
|
+
className='pointer mg1l'
|
|
21
|
+
onClick={handleCopy}
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
<div>Path: {serverInfo.path}</div>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
notification[type]({
|
|
30
|
+
message,
|
|
31
|
+
description: desc,
|
|
32
|
+
duration
|
|
33
|
+
})
|
|
34
|
+
}
|
package/client/css/basic.styl
CHANGED
package/client/store/common.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import handleError from '../common/error-handler'
|
|
6
6
|
import { Modal } from 'antd'
|
|
7
|
-
import { debounce, some, get } from 'lodash-es'
|
|
7
|
+
import { debounce, some, get, pickBy } from 'lodash-es'
|
|
8
8
|
import {
|
|
9
9
|
modals,
|
|
10
10
|
leftSidebarWidthKey,
|
|
@@ -219,27 +219,31 @@ export default Store => {
|
|
|
219
219
|
delete p.name
|
|
220
220
|
delete p.id
|
|
221
221
|
if (type === connectionMap.rdp) {
|
|
222
|
+
const filtered = pickBy(p.rdp, (value) => value !== undefined && value !== '')
|
|
222
223
|
return {
|
|
223
224
|
...tab,
|
|
224
|
-
...
|
|
225
|
+
...filtered
|
|
225
226
|
}
|
|
226
227
|
} else if (type === connectionMap.vnc) {
|
|
228
|
+
const filtered = pickBy(p.vnc, (value) => value !== undefined && value !== '')
|
|
227
229
|
return {
|
|
228
230
|
...tab,
|
|
229
|
-
...
|
|
231
|
+
...filtered
|
|
230
232
|
}
|
|
231
233
|
} else if (type === connectionMap.telnet) {
|
|
234
|
+
const filtered = pickBy(p.telnet, (value) => value !== undefined && value !== '')
|
|
232
235
|
return {
|
|
233
236
|
...tab,
|
|
234
|
-
...
|
|
237
|
+
...filtered
|
|
235
238
|
}
|
|
236
239
|
}
|
|
237
240
|
delete p.rdp
|
|
238
241
|
delete p.vnc
|
|
239
242
|
delete p.telnet
|
|
243
|
+
const filtered = pickBy(p, (value) => value !== undefined && value !== '')
|
|
240
244
|
return {
|
|
241
245
|
...tab,
|
|
242
|
-
...
|
|
246
|
+
...filtered
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
Store.prototype.applyProfileToTabs = function (tab) {
|
|
@@ -75,6 +75,10 @@ export default () => {
|
|
|
75
75
|
resolutions: ls.getItemJSON(resolutionsLsKey, []),
|
|
76
76
|
terminalCommandHistory: new Set(ls.getItemJSON(cmdHistoryKey, [])),
|
|
77
77
|
|
|
78
|
+
// workspaces
|
|
79
|
+
workspaces: [],
|
|
80
|
+
workspaceSaveModalVisible: false,
|
|
81
|
+
|
|
78
82
|
// init session control
|
|
79
83
|
selectedSessions: [],
|
|
80
84
|
sessionModalVisible: false,
|