@electerm/electerm-react 3.9.15 → 3.10.0
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 +3 -2
- package/client/components/ai/ai-config.jsx +9 -1
- package/client/components/batch-op/batch-op-runner.jsx +1 -2
- package/client/components/bookmark-form/common/bookmark-group-tree-format.js +1 -1
- package/client/components/bookmark-form/common/bookmark-select.jsx +1 -1
- package/client/components/bookmark-form/tree-select.jsx +1 -1
- package/client/components/setting-panel/setting-terminal.jsx +12 -0
- package/client/components/setting-panel/start-session-select.jsx +1 -1
- package/client/components/terminal/drop-file-modal.jsx +3 -3
- package/client/components/terminal/terminal-apis.js +8 -0
- package/client/components/terminal/terminal-interactive.jsx +69 -30
- package/client/components/terminal/terminal.jsx +28 -14
- package/client/components/terminal-info/base.jsx +25 -14
- package/client/components/terminal-info/log-path-edit.jsx +86 -0
- package/client/components/widgets/widget-form.jsx +3 -0
- package/package.json +1 -1
|
@@ -64,7 +64,7 @@ export default {
|
|
|
64
64
|
],
|
|
65
65
|
hideIP: false,
|
|
66
66
|
dataSyncSelected: 'all',
|
|
67
|
-
baseURLAI: 'https://api.
|
|
67
|
+
baseURLAI: 'https://api.atlascloud.ai/v1',
|
|
68
68
|
modelAI: 'deepseek-chat',
|
|
69
69
|
roleAI: '终端专家,提供不同系统下命令,简要解释用法,用markdown格式',
|
|
70
70
|
apiPathAI: '/chat/completions',
|
|
@@ -75,5 +75,6 @@ export default {
|
|
|
75
75
|
autoReconnectTerminal: false,
|
|
76
76
|
startDirectoryLocal: '',
|
|
77
77
|
allowMultiInstance: false,
|
|
78
|
-
disableDeveloperTool: false
|
|
78
|
+
disableDeveloperTool: false,
|
|
79
|
+
dragDropBehavior: 'ask'
|
|
79
80
|
}
|
|
@@ -36,6 +36,7 @@ const proxyOptions = [
|
|
|
36
36
|
|
|
37
37
|
export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig }) {
|
|
38
38
|
const [form] = Form.useForm()
|
|
39
|
+
const baseURLAI = Form.useWatch('baseURLAI', form)
|
|
39
40
|
|
|
40
41
|
useEffect(() => {
|
|
41
42
|
if (initialValues) {
|
|
@@ -67,6 +68,13 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
67
68
|
return { label, title }
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
function renderApiUrlLabel () {
|
|
72
|
+
if (baseURLAI === 'https://api.atlascloud.ai/v1') {
|
|
73
|
+
return <span>API URL (<Link to='https://atlascloud.ai'>AtlasCloud</Link>)</span>
|
|
74
|
+
}
|
|
75
|
+
return 'API URL'
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
if (!showAIConfig) {
|
|
71
79
|
return null
|
|
72
80
|
}
|
|
@@ -90,7 +98,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
90
98
|
layout='vertical'
|
|
91
99
|
className='ai-config-form'
|
|
92
100
|
>
|
|
93
|
-
<Form.Item label=
|
|
101
|
+
<Form.Item label={renderApiUrlLabel()} required>
|
|
94
102
|
<Space.Compact className='width-100'>
|
|
95
103
|
<Form.Item
|
|
96
104
|
label='API URL'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component } from 'react'
|
|
2
|
-
import { refsStatic } from '../common/ref'
|
|
2
|
+
import { refsStatic, refs } from '../common/ref'
|
|
3
3
|
import { statusMap } from '../../common/constants'
|
|
4
4
|
import { autoRun } from 'manate'
|
|
5
5
|
import uid from '../../common/uid'
|
|
@@ -237,7 +237,6 @@ export default class BatchOpRunner extends Component {
|
|
|
237
237
|
throw new Error('No active tab. Please connect first.')
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
const { refs } = await import('../common/ref')
|
|
241
240
|
const term = refs.get('term-' + tabId)
|
|
242
241
|
if (!term || !term.term) {
|
|
243
242
|
throw new Error('Terminal not found')
|
|
@@ -21,7 +21,7 @@ export default (bookmarkGroups = [], disabledId = '', returnMap = false, current
|
|
|
21
21
|
}
|
|
22
22
|
return y
|
|
23
23
|
}
|
|
24
|
-
const level1 = bookmarkGroups.filter(d => d.level
|
|
24
|
+
const level1 = bookmarkGroups.filter(d => d.level === 1 || !d.level)
|
|
25
25
|
.map(d => {
|
|
26
26
|
const r = {
|
|
27
27
|
title: d.title,
|
|
@@ -38,7 +38,7 @@ function buildTreeData (bookmarkGroups, tree) {
|
|
|
38
38
|
if (!x) return ''
|
|
39
39
|
return { value: x.id, key: x.id, title: createTitle(x) }
|
|
40
40
|
}
|
|
41
|
-
const level1 = cats.filter(d => d.level
|
|
41
|
+
const level1 = cats.filter(d => d.level === 1 || !d.level)
|
|
42
42
|
.map(d => {
|
|
43
43
|
const r = {
|
|
44
44
|
title: d.title,
|
|
@@ -79,7 +79,7 @@ function buildData (bookmarks, bookmarkGroups, searchText = '') {
|
|
|
79
79
|
title: createTitleWithTag(x)
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
const level1 = cats.filter(d => d.level
|
|
82
|
+
const level1 = cats.filter(d => d.level === 1 || !d.level)
|
|
83
83
|
.map(d => {
|
|
84
84
|
const children = [
|
|
85
85
|
...(d.bookmarkGroupIds || []).map(buildSubCats),
|
|
@@ -84,6 +84,7 @@ export default class SettingTerminal extends Component {
|
|
|
84
84
|
|
|
85
85
|
handleChangeDelMode = v => this.onChangeValue(v, 'backspaceMode')
|
|
86
86
|
handleChangeRenderType = v => this.onChangeValue(v, 'rendererType')
|
|
87
|
+
handleChangeDragDropBehavior = v => this.onChangeValue(v, 'dragDropBehavior')
|
|
87
88
|
|
|
88
89
|
handleChangeFont = (values) => {
|
|
89
90
|
this.onChangeValue(
|
|
@@ -471,6 +472,7 @@ export default class SettingTerminal extends Component {
|
|
|
471
472
|
const {
|
|
472
473
|
rendererType,
|
|
473
474
|
backspaceMode = '^?',
|
|
475
|
+
dragDropBehavior = 'ask',
|
|
474
476
|
keywords = [{ color: 'red' }]
|
|
475
477
|
} = this.props.config
|
|
476
478
|
const {
|
|
@@ -593,6 +595,16 @@ export default class SettingTerminal extends Component {
|
|
|
593
595
|
'autoReconnectTerminal'
|
|
594
596
|
].map(d => this.renderToggle(d))
|
|
595
597
|
}
|
|
598
|
+
<div className='pd1b'>{e('dragDropBehavior')}</div>
|
|
599
|
+
<Select
|
|
600
|
+
onChange={this.handleChangeDragDropBehavior}
|
|
601
|
+
value={dragDropBehavior}
|
|
602
|
+
popupMatchSelectWidth={false}
|
|
603
|
+
>
|
|
604
|
+
{['ask', 'trz', 'rz', 'inputOnly'].map(id => (
|
|
605
|
+
<Option key={id} value={id}>{e(id)}</Option>
|
|
606
|
+
))}
|
|
607
|
+
</Select>
|
|
596
608
|
<div className='pd1b'>{e('terminalBackSpaceMode')}</div>
|
|
597
609
|
<Select
|
|
598
610
|
onChange={this.handleChangeDelMode}
|
|
@@ -26,21 +26,21 @@ export class DropFileModal extends Component {
|
|
|
26
26
|
<button
|
|
27
27
|
type='button'
|
|
28
28
|
className='custom-modal-ok-btn'
|
|
29
|
-
onClick={() => onSelect('
|
|
29
|
+
onClick={() => onSelect('trz')}
|
|
30
30
|
>
|
|
31
31
|
trz
|
|
32
32
|
</button>
|
|
33
33
|
<button
|
|
34
34
|
type='button'
|
|
35
35
|
className='custom-modal-cancel-btn'
|
|
36
|
-
onClick={() => onSelect('
|
|
36
|
+
onClick={() => onSelect('rz')}
|
|
37
37
|
>
|
|
38
38
|
rz
|
|
39
39
|
</button>
|
|
40
40
|
<button
|
|
41
41
|
type='button'
|
|
42
42
|
className='custom-modal-cancel-btn'
|
|
43
|
-
onClick={() => onSelect('
|
|
43
|
+
onClick={() => onSelect('inputOnly')}
|
|
44
44
|
>
|
|
45
45
|
{e('inputOnly')}
|
|
46
46
|
</button>
|
|
@@ -48,6 +48,13 @@ export default function TermInteractive () {
|
|
|
48
48
|
function onOk () {
|
|
49
49
|
form.submit()
|
|
50
50
|
}
|
|
51
|
+
function onConfirm () {
|
|
52
|
+
window.et.commonWs.s({
|
|
53
|
+
id: opts.id,
|
|
54
|
+
results: [opts.options.confirmResult || 'yes']
|
|
55
|
+
})
|
|
56
|
+
clear()
|
|
57
|
+
}
|
|
51
58
|
function onIgnore () {
|
|
52
59
|
window.et.commonWs.s({
|
|
53
60
|
id: opts.id,
|
|
@@ -91,6 +98,32 @@ export default function TermInteractive () {
|
|
|
91
98
|
</FormItem>
|
|
92
99
|
)
|
|
93
100
|
}
|
|
101
|
+
function renderConfirmBody () {
|
|
102
|
+
const instructions = opts.options.instructions || []
|
|
103
|
+
return (
|
|
104
|
+
<div>
|
|
105
|
+
{
|
|
106
|
+
instructions.map((note, index) => {
|
|
107
|
+
return <pre key={note + index}>{note}</pre>
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
<FormItem>
|
|
111
|
+
<Button
|
|
112
|
+
type='primary'
|
|
113
|
+
onClick={onConfirm}
|
|
114
|
+
>
|
|
115
|
+
{opts.options.submitText || e('submit')}
|
|
116
|
+
</Button>
|
|
117
|
+
<Button
|
|
118
|
+
className='mg1l'
|
|
119
|
+
onClick={onCancel}
|
|
120
|
+
>
|
|
121
|
+
{opts.options.cancelText || e('cancel')}
|
|
122
|
+
</Button>
|
|
123
|
+
</FormItem>
|
|
124
|
+
</div>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
94
127
|
async function initWatch () {
|
|
95
128
|
let done = false
|
|
96
129
|
while (!done) {
|
|
@@ -125,36 +158,42 @@ export default function TermInteractive () {
|
|
|
125
158
|
<Modal
|
|
126
159
|
{...props}
|
|
127
160
|
>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
{
|
|
162
|
+
opts.options?.mode === 'confirm'
|
|
163
|
+
? renderConfirmBody()
|
|
164
|
+
: (
|
|
165
|
+
<Form
|
|
166
|
+
form={form}
|
|
167
|
+
layout='vertical'
|
|
168
|
+
onFinish={onFinish}
|
|
169
|
+
>
|
|
170
|
+
{
|
|
171
|
+
opts.options.prompts.map(renderFormItem)
|
|
172
|
+
}
|
|
173
|
+
<FormItem>
|
|
174
|
+
<Button
|
|
175
|
+
type='primary'
|
|
176
|
+
htmlType='submit'
|
|
177
|
+
>
|
|
178
|
+
{e('submit')}
|
|
179
|
+
</Button>
|
|
180
|
+
<Button
|
|
181
|
+
type='dashed'
|
|
182
|
+
className='mg1l'
|
|
183
|
+
onClick={onIgnore}
|
|
184
|
+
>
|
|
185
|
+
{e('ignore')}
|
|
186
|
+
</Button>
|
|
187
|
+
<Button
|
|
188
|
+
className='mg1l'
|
|
189
|
+
onClick={onCancel}
|
|
190
|
+
>
|
|
191
|
+
{e('cancel')}
|
|
192
|
+
</Button>
|
|
193
|
+
</FormItem>
|
|
194
|
+
</Form>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
158
197
|
</Modal>
|
|
159
198
|
)
|
|
160
199
|
}
|
|
@@ -70,6 +70,7 @@ class Term extends Component {
|
|
|
70
70
|
hasSelection: false,
|
|
71
71
|
saveTerminalLogToFile: !!this.props.config.saveTerminalLogToFile,
|
|
72
72
|
addTimeStampToTermLog: !!this.props.config.addTimeStampToTermLog,
|
|
73
|
+
logPath: this.props.config.sessionLogPath || createDefaultLogPath(),
|
|
73
74
|
passType: 'password',
|
|
74
75
|
lines: [],
|
|
75
76
|
searchResults: [],
|
|
@@ -400,10 +401,15 @@ class Term extends Component {
|
|
|
400
401
|
return
|
|
401
402
|
}
|
|
402
403
|
if (isSshTerminal) {
|
|
403
|
-
this.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
404
|
+
const behavior = this.props.config.dragDropBehavior || 'ask'
|
|
405
|
+
if (behavior === 'ask') {
|
|
406
|
+
this.setState({
|
|
407
|
+
dropFileModalVisible: true,
|
|
408
|
+
droppedFiles: [{ path: filePath, isRemote: true }]
|
|
409
|
+
})
|
|
410
|
+
} else {
|
|
411
|
+
this.handleDropFileAction(behavior, [{ path: filePath, isRemote: true }])
|
|
412
|
+
}
|
|
407
413
|
return
|
|
408
414
|
}
|
|
409
415
|
this.attachAddon._sendData(`"${filePath}" `)
|
|
@@ -425,10 +431,15 @@ class Term extends Component {
|
|
|
425
431
|
}
|
|
426
432
|
|
|
427
433
|
if (isSshTerminal) {
|
|
428
|
-
this.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
434
|
+
const behavior = this.props.config.dragDropBehavior || 'ask'
|
|
435
|
+
if (behavior === 'ask') {
|
|
436
|
+
this.setState({
|
|
437
|
+
dropFileModalVisible: true,
|
|
438
|
+
droppedFiles: filePaths.map(path => ({ path, isRemote: false }))
|
|
439
|
+
})
|
|
440
|
+
} else {
|
|
441
|
+
this.handleDropFileAction(behavior, filePaths.map(path => ({ path, isRemote: false })))
|
|
442
|
+
}
|
|
432
443
|
return
|
|
433
444
|
}
|
|
434
445
|
|
|
@@ -444,8 +455,8 @@ class Term extends Component {
|
|
|
444
455
|
})
|
|
445
456
|
}
|
|
446
457
|
|
|
447
|
-
handleDropFileAction = (action) => {
|
|
448
|
-
const
|
|
458
|
+
handleDropFileAction = (action, filesOverride) => {
|
|
459
|
+
const droppedFiles = filesOverride || this.state.droppedFiles
|
|
449
460
|
if (!droppedFiles || !droppedFiles.length) {
|
|
450
461
|
this.handleDropFileModalCancel()
|
|
451
462
|
return
|
|
@@ -454,7 +465,7 @@ class Term extends Component {
|
|
|
454
465
|
const filePaths = droppedFiles.map(f => f.path)
|
|
455
466
|
|
|
456
467
|
switch (action) {
|
|
457
|
-
case '
|
|
468
|
+
case 'trz': {
|
|
458
469
|
if (this.trzszClient && this.trzszClient.isActive) {
|
|
459
470
|
message.warning('A transfer is already in progress')
|
|
460
471
|
this.handleDropFileModalCancel()
|
|
@@ -464,7 +475,7 @@ class Term extends Component {
|
|
|
464
475
|
this.attachAddon._sendData('trz\r')
|
|
465
476
|
break
|
|
466
477
|
}
|
|
467
|
-
case '
|
|
478
|
+
case 'rz':{
|
|
468
479
|
if (this.zmodemClient && this.zmodemClient.isActive) {
|
|
469
480
|
message.warning('A transfer is already in progress')
|
|
470
481
|
this.handleDropFileModalCancel()
|
|
@@ -474,7 +485,7 @@ class Term extends Component {
|
|
|
474
485
|
this.attachAddon._sendData('rz\r')
|
|
475
486
|
break
|
|
476
487
|
}
|
|
477
|
-
case '
|
|
488
|
+
case 'inputOnly':
|
|
478
489
|
default: {
|
|
479
490
|
const filesAll = filePaths.map(path => `"${path}"`).join(' ')
|
|
480
491
|
this.attachAddon._sendData(filesAll)
|
|
@@ -825,6 +836,9 @@ class Term extends Component {
|
|
|
825
836
|
if (currentCmd && currentCmd.trim() && this.shouldUseManualHistory()) {
|
|
826
837
|
window.store.addCmdHistory(currentCmd.trim())
|
|
827
838
|
}
|
|
839
|
+
if (currentCmd && currentCmd.trim() === 'exit') {
|
|
840
|
+
this.userTypeExit = true
|
|
841
|
+
}
|
|
828
842
|
this.closeSuggestions()
|
|
829
843
|
}
|
|
830
844
|
}
|
|
@@ -1223,7 +1237,7 @@ class Term extends Component {
|
|
|
1223
1237
|
...extra,
|
|
1224
1238
|
...execOpts,
|
|
1225
1239
|
logName,
|
|
1226
|
-
sessionLogPath:
|
|
1240
|
+
sessionLogPath: this.state.logPath,
|
|
1227
1241
|
...pick(config, [
|
|
1228
1242
|
'addTimeStampToTermLog',
|
|
1229
1243
|
'keepaliveCountMax',
|
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
* show base terminal info, id sessionID
|
|
3
3
|
*/
|
|
4
4
|
import { Component } from 'react'
|
|
5
|
-
import { osResolve } from '../../common/resolve'
|
|
6
5
|
import {
|
|
7
6
|
Switch,
|
|
8
7
|
Space,
|
|
9
8
|
Button
|
|
10
9
|
} from 'antd'
|
|
11
|
-
import ShowItem from '../common/show-item'
|
|
12
10
|
import defaults from '../../common/default-setting'
|
|
13
|
-
import { toggleTerminalLog, toggleTerminalLogTimestamp } from '../terminal/terminal-apis'
|
|
11
|
+
import { toggleTerminalLog, toggleTerminalLogTimestamp, setTerminalLogPath } from '../terminal/terminal-apis'
|
|
14
12
|
import {
|
|
15
13
|
ClockCircleOutlined,
|
|
16
14
|
BorderlessTableOutlined,
|
|
@@ -19,8 +17,8 @@ import {
|
|
|
19
17
|
ApiOutlined,
|
|
20
18
|
PartitionOutlined
|
|
21
19
|
} from '@ant-design/icons'
|
|
22
|
-
import createDefaultSessionLogPath from '../../common/default-log-path'
|
|
23
20
|
import { refs } from '../common/ref'
|
|
21
|
+
import LogPathEdit from './log-path-edit'
|
|
24
22
|
|
|
25
23
|
const e = window.translate
|
|
26
24
|
|
|
@@ -36,7 +34,8 @@ const mapper = {
|
|
|
36
34
|
export default class TerminalInfoBase extends Component {
|
|
37
35
|
state = {
|
|
38
36
|
saveTerminalLogToFile: false,
|
|
39
|
-
addTimeStampToTermLog: false
|
|
37
|
+
addTimeStampToTermLog: false,
|
|
38
|
+
logPath: ''
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
componentDidMount () {
|
|
@@ -75,6 +74,17 @@ export default class TerminalInfoBase extends Component {
|
|
|
75
74
|
})
|
|
76
75
|
}
|
|
77
76
|
|
|
77
|
+
onLogPathChange = (v) => {
|
|
78
|
+
const { pid } = this.props
|
|
79
|
+
setTerminalLogPath(pid, v)
|
|
80
|
+
refs.get('term-' + pid)?.setState({
|
|
81
|
+
logPath: v
|
|
82
|
+
})
|
|
83
|
+
this.setState({
|
|
84
|
+
logPath: v
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
78
88
|
handleToggle = () => {
|
|
79
89
|
const { saveTerminalLogToFile, addTimeStampToTermLog } = this.state
|
|
80
90
|
const {
|
|
@@ -101,7 +111,8 @@ export default class TerminalInfoBase extends Component {
|
|
|
101
111
|
if (term) {
|
|
102
112
|
this.setState({
|
|
103
113
|
saveTerminalLogToFile: term.state.saveTerminalLogToFile,
|
|
104
|
-
addTimeStampToTermLog: term.state.addTimeStampToTermLog
|
|
114
|
+
addTimeStampToTermLog: term.state.addTimeStampToTermLog,
|
|
115
|
+
logPath: term.state.logPath
|
|
105
116
|
})
|
|
106
117
|
} else {
|
|
107
118
|
this.timer = setTimeout(this.getState, 100)
|
|
@@ -156,15 +167,10 @@ export default class TerminalInfoBase extends Component {
|
|
|
156
167
|
const {
|
|
157
168
|
id,
|
|
158
169
|
logName,
|
|
159
|
-
|
|
170
|
+
pid
|
|
160
171
|
} = this.props
|
|
161
|
-
const { saveTerminalLogToFile } = this.state
|
|
162
|
-
const base = sessionLogPath || createDefaultSessionLogPath()
|
|
163
|
-
const path = osResolve(base, logName + '.log')
|
|
172
|
+
const { saveTerminalLogToFile, logPath } = this.state
|
|
164
173
|
const name = e('saveTerminalLogToFile')
|
|
165
|
-
const to = saveTerminalLogToFile
|
|
166
|
-
? <ShowItem disabled={!saveTerminalLogToFile} to={path}>{path}</ShowItem>
|
|
167
|
-
: path
|
|
168
174
|
return (
|
|
169
175
|
<div className='terminal-info-section terminal-info-base'>
|
|
170
176
|
<div className='fix'>
|
|
@@ -187,7 +193,12 @@ export default class TerminalInfoBase extends Component {
|
|
|
187
193
|
this.renderInfoSelection()
|
|
188
194
|
}
|
|
189
195
|
</div>
|
|
190
|
-
<
|
|
196
|
+
<LogPathEdit
|
|
197
|
+
pid={pid}
|
|
198
|
+
logPath={logPath}
|
|
199
|
+
logName={logName}
|
|
200
|
+
setLogPath={this.onLogPathChange}
|
|
201
|
+
/>
|
|
191
202
|
</div>
|
|
192
203
|
)
|
|
193
204
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-terminal log path editor
|
|
3
|
+
* Reads/writes logPath on the terminal's own state via refs
|
|
4
|
+
*/
|
|
5
|
+
import { Button } from 'antd'
|
|
6
|
+
import message from '../common/message'
|
|
7
|
+
import InputConfirm from '../common/input-confirm'
|
|
8
|
+
import ShowItem from '../common/show-item'
|
|
9
|
+
import { chooseSaveDirectory } from '../../common/choose-save-folder'
|
|
10
|
+
import createDefaultLogPath from '../../common/default-log-path'
|
|
11
|
+
import { osResolve } from '../../common/resolve'
|
|
12
|
+
|
|
13
|
+
const e = window.translate
|
|
14
|
+
|
|
15
|
+
export default function LogPathEdit ({ pid, logPath, logName, setLogPath }) {
|
|
16
|
+
const defaultPath = createDefaultLogPath()
|
|
17
|
+
const base = logPath || defaultPath
|
|
18
|
+
const fullPath = osResolve(base, logName + '.log')
|
|
19
|
+
|
|
20
|
+
const testAndSet = async (v) => {
|
|
21
|
+
if (v) {
|
|
22
|
+
try {
|
|
23
|
+
const { fs } = window
|
|
24
|
+
const uid = 'test-' + Date.now()
|
|
25
|
+
const testFile = osResolve(v, uid + '.test.log')
|
|
26
|
+
await fs.touch(testFile)
|
|
27
|
+
await fs.unlink(testFile)
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.log('log path test failed', err)
|
|
30
|
+
message.error('invalid log folder')
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
setLogPath(v)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleChange = (v) => {
|
|
38
|
+
testAndSet(v)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleChooseFolder = async () => {
|
|
42
|
+
const path = await chooseSaveDirectory()
|
|
43
|
+
if (path) {
|
|
44
|
+
handleChange(path)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleReset = () => {
|
|
49
|
+
setLogPath('')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const inputProps = {
|
|
53
|
+
value: logPath,
|
|
54
|
+
placeholder: defaultPath,
|
|
55
|
+
onChange: handleChange,
|
|
56
|
+
addonAfter: (
|
|
57
|
+
<>
|
|
58
|
+
<Button
|
|
59
|
+
onClick={handleChooseFolder}
|
|
60
|
+
className='mg1r'
|
|
61
|
+
type='text'
|
|
62
|
+
size='small'
|
|
63
|
+
>
|
|
64
|
+
{e('chooseFolder')}
|
|
65
|
+
</Button>
|
|
66
|
+
<Button
|
|
67
|
+
size='small'
|
|
68
|
+
type='text'
|
|
69
|
+
onClick={handleReset}
|
|
70
|
+
>
|
|
71
|
+
{e('reset')}
|
|
72
|
+
</Button>
|
|
73
|
+
</>
|
|
74
|
+
),
|
|
75
|
+
prefix: e('terminalLogPath') + ': '
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className='pd1b'>
|
|
80
|
+
<InputConfirm {...inputProps} />
|
|
81
|
+
<div className='pd1t font-xs color-grey'>
|
|
82
|
+
{fullPath} <ShowItem to={fullPath} />
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
@@ -50,6 +50,9 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
|
|
|
50
50
|
case 'string':
|
|
51
51
|
control = <Input placeholder={description} />
|
|
52
52
|
break
|
|
53
|
+
case 'textarea':
|
|
54
|
+
control = <Input.TextArea autoSize={{ minRows: 3 }} placeholder={description} />
|
|
55
|
+
break
|
|
53
56
|
case 'number':
|
|
54
57
|
control = <InputNumber style={{ width: '100%' }} placeholder={description} />
|
|
55
58
|
break
|