@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.
@@ -64,7 +64,7 @@ export default {
64
64
  ],
65
65
  hideIP: false,
66
66
  dataSyncSelected: 'all',
67
- baseURLAI: 'https://api.deepseek.com',
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='API URL' required>
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 !== 2)
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 !== 2)
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 !== 2)
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}
@@ -65,7 +65,7 @@ function BookmarkSelect (props) {
65
65
  title: createTitleWithTag(x)
66
66
  }
67
67
  }
68
- const level1 = cats.filter(d => d.level !== 2)
68
+ const level1 = cats.filter(d => d.level === 1 || !d.level)
69
69
  .map(d => {
70
70
  const r = {
71
71
  title: d.title,
@@ -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('trzUpload')}
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('rzUpload')}
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('inputPath')}
43
+ onClick={() => onSelect('inputOnly')}
44
44
  >
45
45
  {e('inputOnly')}
46
46
  </button>
@@ -41,3 +41,11 @@ export function toggleTerminalLogTimestamp (pid) {
41
41
  action: 'toggle-terminal-log-timestamp'
42
42
  })
43
43
  }
44
+
45
+ export function setTerminalLogPath (pid, logPath) {
46
+ return fetch({
47
+ pid,
48
+ logPath,
49
+ action: 'set-terminal-log-path'
50
+ })
51
+ }
@@ -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
- <Form
129
- form={form}
130
- layout='vertical'
131
- onFinish={onFinish}
132
- >
133
- {
134
- opts.options.prompts.map(renderFormItem)
135
- }
136
- <FormItem>
137
- <Button
138
- type='primary'
139
- htmlType='submit'
140
- >
141
- {e('submit')}
142
- </Button>
143
- <Button
144
- type='dashed'
145
- className='mg1l'
146
- onClick={onIgnore}
147
- >
148
- {e('ignore')}
149
- </Button>
150
- <Button
151
- className='mg1l'
152
- onClick={onCancel}
153
- >
154
- {e('cancel')}
155
- </Button>
156
- </FormItem>
157
- </Form>
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.setState({
404
- dropFileModalVisible: true,
405
- droppedFiles: [{ path: filePath, isRemote: true }]
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.setState({
429
- dropFileModalVisible: true,
430
- droppedFiles: filePaths.map(path => ({ path, isRemote: false }))
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 { droppedFiles } = this.state
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 'trzUpload': {
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 'rzUpload': {
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 'inputPath':
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: config.sessionLogPath || createDefaultLogPath(),
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
- sessionLogPath
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
- <p><b>log:</b> {to}</p>
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.9.15",
3
+ "version": "3.10.0",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",