@electerm/electerm-react 3.8.8 → 3.8.15

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.
@@ -21,6 +21,12 @@
21
21
 
22
22
  const SUPPORTED_PROTOCOLS = ['ssh', 'telnet', 'vnc', 'rdp', 'spice', 'serial', 'ftp', 'http', 'https', 'electerm']
23
23
 
24
+ /**
25
+ * Deny list for opts keys - these are parsed from the URL itself
26
+ * and should not be overridable via the opts JSON parameter for safety
27
+ */
28
+ const OPTS_DENY_LIST = ['type', 'host']
29
+
24
30
  /**
25
31
  * Default ports for each protocol
26
32
  */
@@ -393,6 +399,7 @@ function parseQuickConnect (str) {
393
399
  if (optsStr) {
394
400
  try {
395
401
  const extraOpts = JSON.parse(optsStr)
402
+ OPTS_DENY_LIST.forEach(key => delete extraOpts[key])
396
403
  Object.assign(opts, extraOpts)
397
404
  } catch (err) {
398
405
  console.error('Failed to parse opts:', err)
@@ -439,5 +446,6 @@ export {
439
446
  getDefaultPort,
440
447
  getSupportedProtocols,
441
448
  SUPPORTED_PROTOCOLS,
442
- DEFAULT_PORTS
449
+ DEFAULT_PORTS,
450
+ OPTS_DENY_LIST
443
451
  }
@@ -0,0 +1,52 @@
1
+ import Modal from '../common/modal'
2
+ import { auto } from 'manate/react'
3
+ import AIConfigForm from './ai-config'
4
+ import message from '../common/message'
5
+ import { aiConfigsArr } from './ai-config-props'
6
+ import { pick } from 'lodash-es'
7
+
8
+ const e = window.translate
9
+
10
+ export default auto(function AIConfigModal ({ store }) {
11
+ const { showAIConfigModal } = store
12
+
13
+ if (!showAIConfigModal) {
14
+ return null
15
+ }
16
+
17
+ function getInitialValues () {
18
+ const res = pick(store.config, aiConfigsArr)
19
+ if (!res.languageAI) {
20
+ res.languageAI = window.store.getLangName()
21
+ }
22
+ return res
23
+ }
24
+
25
+ function handleSubmit (values) {
26
+ window.store.updateConfig(values)
27
+ message.success(e('saved') || 'Saved')
28
+ window.store.showAIConfigModal = false
29
+ }
30
+
31
+ function handleClose () {
32
+ window.store.showAIConfigModal = false
33
+ }
34
+
35
+ return (
36
+ <Modal
37
+ open={showAIConfigModal}
38
+ onCancel={handleClose}
39
+ footer={null}
40
+ title='AI Config'
41
+ width='80%'
42
+ destroyOnClose
43
+ className='ai-config-modal'
44
+ >
45
+ <AIConfigForm
46
+ initialValues={getInitialValues()}
47
+ onSubmit={handleSubmit}
48
+ showAIConfig
49
+ />
50
+ </Modal>
51
+ )
52
+ })
@@ -6,7 +6,7 @@ import {
6
6
  Alert,
7
7
  Space
8
8
  } from 'antd'
9
- import { useEffect, useState } from 'react'
9
+ import { useEffect } from 'react'
10
10
  import Link from '../common/external-link'
11
11
  import AiCache from './ai-cache'
12
12
  import {
@@ -15,9 +15,6 @@ import {
15
15
  import Password from '../common/password'
16
16
  import AiHistory, { addHistoryItem } from './ai-history'
17
17
 
18
- // Comprehensive API provider configurations
19
- import providers from './providers'
20
-
21
18
  const STORAGE_KEY_CONFIG = 'ai_config_history'
22
19
  const EVENT_NAME_CONFIG = 'ai-config-history-update'
23
20
 
@@ -39,7 +36,6 @@ const proxyOptions = [
39
36
 
40
37
  export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig }) {
41
38
  const [form] = Form.useForm()
42
- const [modelOptions, setModelOptions] = useState([])
43
39
 
44
40
  useEffect(() => {
45
41
  if (initialValues) {
@@ -51,23 +47,6 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
51
47
  return true
52
48
  }
53
49
 
54
- const getBaseURLOptions = () => {
55
- return providers.map(provider => ({
56
- value: provider.baseURL,
57
- label: provider.label
58
- }))
59
- }
60
-
61
- const getModelOptions = (baseURL) => {
62
- const provider = providers.find(p => p.baseURL === baseURL)
63
- if (!provider) return []
64
-
65
- return provider.models.map(model => ({
66
- value: model,
67
- label: model
68
- }))
69
- }
70
-
71
50
  const handleSubmit = async (values) => {
72
51
  onSubmit(values)
73
52
  addHistoryItem(STORAGE_KEY_CONFIG, values, EVENT_NAME_CONFIG)
@@ -88,11 +67,6 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
88
67
  return { label, title }
89
68
  }
90
69
 
91
- function handleChange (v) {
92
- const options = getModelOptions(v)
93
- setModelOptions(options)
94
- }
95
-
96
70
  if (!showAIConfig) {
97
71
  return null
98
72
  }
@@ -127,12 +101,8 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
127
101
  { type: 'url', message: 'Please enter a valid URL!' }
128
102
  ]}
129
103
  >
130
- <AutoComplete
131
- options={getBaseURLOptions()}
132
- placeholder='Enter or select API provider URL'
133
- filterOption={filter}
134
- onChange={handleChange}
135
- allowClear
104
+ <Input
105
+ placeholder='Enter API provider URL'
136
106
  style={{ width: '75%' }}
137
107
  />
138
108
  </Form.Item>
@@ -156,10 +126,8 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
156
126
  name='modelAI'
157
127
  rules={[{ required: true, message: 'Please input or select a model!' }]}
158
128
  >
159
- <AutoComplete
160
- options={modelOptions}
129
+ <Input
161
130
  placeholder='Enter or select AI model'
162
- filterOption={filter}
163
131
  />
164
132
  </Form.Item>
165
133
 
@@ -202,11 +170,10 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
202
170
  >
203
171
  <AutoComplete
204
172
  options={proxyOptions}
205
- placeholder='Enter proxy URL (optional)'
206
173
  filterOption={filter}
207
174
  allowClear
208
175
  >
209
- <Input />
176
+ <Input placeholder='Enter proxy URL (optional)' />
210
177
  </AutoComplete>
211
178
  </Form.Item>
212
179
 
@@ -1,15 +1,4 @@
1
- import providers from './providers'
2
-
3
1
  export default function getBrand (baseURLAI) {
4
- // First, try to match with providers
5
- const provider = providers.find(p => p.baseURL === baseURLAI)
6
- if (provider) {
7
- return {
8
- brand: provider.label,
9
- brandUrl: provider.homepage
10
- }
11
- }
12
-
13
2
  // If no match, extract brand from URL
14
3
  try {
15
4
  const url = new URL(baseURLAI)
@@ -13,7 +13,8 @@ export default function CustomCss (props) {
13
13
  if (configLoaded) {
14
14
  const style = document.getElementById(themeDomId)
15
15
  if (style) {
16
- style.innerHTML = customCss || ''
16
+ const safeCss = (customCss || '').replace(/@import/gi, '#')
17
+ style.innerHTML = safeCss
17
18
  }
18
19
  }
19
20
  }, [customCss, configLoaded])
@@ -28,6 +28,7 @@ import ConnectionHoppingWarning from './connection-hopping-warnning'
28
28
  import SshConfigLoadNotify from '../ssh-config/ssh-config-load-notify'
29
29
  import LoadSshConfigs from '../ssh-config/load-ssh-configs'
30
30
  import AIChat from '../ai/ai-chat'
31
+ import AIConfigModal from '../ai/ai-config-modal'
31
32
  import Opacity from '../common/opacity'
32
33
  import MoveItemModal from '../tree-list/move-item-modal'
33
34
  import InputContextMenu from '../common/input-context-menu'
@@ -295,6 +296,7 @@ export default auto(function Index (props) {
295
296
  <BookmarkFromHistoryModal />
296
297
  <NotificationContainer />
297
298
  <BatchOpRunner />
299
+ <AIConfigModal store={store} />
298
300
  </div>
299
301
  </ConfigProvider>
300
302
  )
@@ -10,12 +10,9 @@ import {
10
10
  import { Button, Space, Dropdown } from 'antd'
11
11
  import copy from 'json-deep-copy'
12
12
  import time from '../../common/time'
13
- import { uniq } from 'lodash-es'
14
- import { fixBookmarks } from '../../common/db-fix'
15
13
  import download from '../../common/download'
16
- import delay from '../../common/wait'
17
- import { action } from 'manate'
18
14
  import Upload from '../common/upload'
15
+ import { beforeBookmarkUpload } from './bookmark-upload'
19
16
 
20
17
  const e = window.translate
21
18
 
@@ -28,71 +25,12 @@ export default function BookmarkToolbar (props) {
28
25
  bookmarkGroups,
29
26
  bookmarks
30
27
  } = props
31
- const upload = action(async (file) => {
32
- const { store } = window
33
- const filePath = file.filePath
34
- const txt = await window.fs.readFile(filePath)
35
-
36
- const content = JSON.parse(txt)
37
- const {
38
- bookmarkGroups: bookmarkGroups1,
39
- bookmarks: bookmarks1
40
- } = content
41
- const bookmarkGroups0 = copy(bookmarkGroups)
42
- const bookmarks0 = copy(bookmarks)
43
-
44
- // Using Map instead of reduce
45
- const bmTree = new Map(
46
- bookmarks0.map(bookmark => [bookmark.id, bookmark])
47
- )
48
- const bmgTree = new Map(
49
- bookmarkGroups0.map(group => [group.id, group])
50
- )
51
-
52
- const fixed = fixBookmarks(bookmarks1)
53
-
54
- fixed.forEach(bg => {
55
- if (!bmTree.has(bg.id)) {
56
- store.bookmarks.push(bg)
57
- }
58
- })
59
-
60
- bookmarkGroups1.forEach(bg => {
61
- if (!bmgTree.has(bg.id)) {
62
- store.bookmarkGroups.push(bg)
63
- } else {
64
- const bg1 = store.bookmarkGroups.find(
65
- b => b.id === bg.id
66
- )
67
- bg1.bookmarkIds = uniq(
68
- [
69
- ...bg1.bookmarkIds,
70
- ...bg.bookmarkIds
71
- ]
72
- )
73
- }
74
- })
75
- return false
76
- })
77
- const beforeUpload = async (file) => {
78
- const names = [
79
- 'bookmarks',
80
- 'bookmarkGroups'
81
- ]
82
- for (const name of names) {
83
- window[`watch${name}`].stop()
84
- }
85
- upload(file)
86
- await delay(1000)
87
- for (const name of names) {
88
- window[`watch${name}`].start()
89
- }
90
- }
28
+ const beforeUpload = beforeBookmarkUpload
91
29
 
92
30
  const handleDownload = () => {
93
31
  const txt = JSON.stringify({
94
- bookmarkGroups: copy(bookmarkGroups),
95
- bookmarks: copy(bookmarks)
32
+ bookmarkGroups: copy(bookmarkGroups || []),
33
+ bookmarks: copy(bookmarks || [])
96
34
  }, null, 2)
97
35
  const stamp = time(undefined, 'YYYY-MM-DD-HH-mm-ss')
98
36
  download('bookmarks-' + stamp + '.json', txt)
@@ -0,0 +1,106 @@
1
+ /**
2
+ * bookmark import/upload logic
3
+ */
4
+
5
+ import copy from 'json-deep-copy'
6
+ import { uniq, isPlainObject } from 'lodash-es'
7
+ import { action } from 'manate'
8
+ import uid from '../../common/uid'
9
+ import time from '../../common/time'
10
+ import { fixBookmarks } from '../../common/db-fix'
11
+ import delay from '../../common/wait'
12
+
13
+ function fixBookmarksId (bookmarks) {
14
+ return bookmarks.map(item => {
15
+ if (!isPlainObject(item)) {
16
+ return null
17
+ }
18
+ if (!item.id) {
19
+ item.id = uid()
20
+ }
21
+ return item
22
+ }).filter(Boolean)
23
+ }
24
+ export const bookmarkUpload = action(async (file) => {
25
+ const { store } = window
26
+ const { bookmarks, bookmarkGroups } = store
27
+
28
+ const filePath = file.filePath
29
+ const txt = await window.fs.readFile(filePath)
30
+
31
+ const content = JSON.parse(txt)
32
+ let bookmarkGroups1 = []
33
+ let bookmarks1 = []
34
+ if (Array.isArray(content)) {
35
+ bookmarks1 = fixBookmarksId(content)
36
+ bookmarkGroups1 = [{
37
+ id: uid(),
38
+ title: 'imported_' + time(),
39
+ color: '#0088cc',
40
+ bookmarkGroupIds: [],
41
+ bookmarkIds: bookmarks1.map(b => b.id)
42
+ }]
43
+ } else {
44
+ bookmarkGroups1 = content.bookmarkGroups || []
45
+ bookmarks1 = fixBookmarksId(content.bookmarks || [])
46
+ }
47
+
48
+ const bookmarkGroups0 = copy(bookmarkGroups)
49
+ const bookmarks0 = copy(bookmarks)
50
+
51
+ const bmTree = new Map(
52
+ bookmarks0.map(bookmark => [bookmark.id, bookmark])
53
+ )
54
+ const bmgTree = new Map(
55
+ bookmarkGroups0.map(group => [group.id, group])
56
+ )
57
+
58
+ const fixed = fixBookmarks(bookmarks1)
59
+
60
+ fixed.forEach(bg => {
61
+ if (!bmTree.has(bg.id)) {
62
+ store.bookmarks.push(bg)
63
+ }
64
+ })
65
+
66
+ bookmarkGroups1.forEach(bg => {
67
+ if (!bmgTree.has(bg.id)) {
68
+ store.bookmarkGroups.push(bg)
69
+ } else {
70
+ const bg1 = store.bookmarkGroups.find(
71
+ b => b.id === bg.id
72
+ )
73
+ bg1.bookmarkIds = uniq(
74
+ [
75
+ ...(bg1.bookmarkIds || []),
76
+ ...(bg.bookmarkIds || [])
77
+ ]
78
+ )
79
+ bg1.bookmarkGroupIds = uniq(
80
+ [
81
+ ...(bg1.bookmarkGroupIds || []),
82
+ ...(bg.bookmarkGroupIds || [])
83
+ ]
84
+ )
85
+ }
86
+ })
87
+
88
+ store.fixBookmarkGroups()
89
+
90
+ return false
91
+ })
92
+
93
+ export async function beforeBookmarkUpload (file) {
94
+ const names = [
95
+ 'bookmarks',
96
+ 'bookmarkGroups'
97
+ ]
98
+ for (const name of names) {
99
+ window[`watch${name}`].stop()
100
+ }
101
+ bookmarkUpload(file)
102
+ await delay(1000)
103
+ for (const name of names) {
104
+ window[`watch${name}`].start()
105
+ }
106
+ }
@@ -10,9 +10,7 @@ import {
10
10
  rightSidebarWidthKey,
11
11
  addPanelWidthLsKey,
12
12
  dismissDelKeyTipLsKey,
13
- connectionMap,
14
- settingMap,
15
- settingAiId
13
+ connectionMap
16
14
  } from '../common/constants'
17
15
  import * as ls from '../common/safe-local-storage'
18
16
  import { refs, refsStatic } from '../components/common/ref'
@@ -20,7 +18,6 @@ import { action } from 'manate'
20
18
  import uid from '../common/uid'
21
19
  import deepCopy from 'json-deep-copy'
22
20
  import { aiConfigsArr } from '../components/ai/ai-config-props'
23
- import settingList from '../common/setting-list'
24
21
 
25
22
  const e = window.translate
26
23
  const { assign } = Object
@@ -54,12 +51,7 @@ export default Store => {
54
51
  }
55
52
 
56
53
  Store.prototype.toggleAIConfig = function () {
57
- const { store } = window
58
- store.storeAssign({
59
- settingTab: settingMap.setting
60
- })
61
- store.setSettingItem(settingList().find(d => d.id === settingAiId))
62
- store.openSettingModal()
54
+ window.store.showAIConfigModal = true
63
55
  }
64
56
 
65
57
  Store.prototype.onResize = debounce(async function () {
@@ -118,6 +118,7 @@ export default () => {
118
118
  rightPanelTab: 'info',
119
119
  rightPanelPinned: false,
120
120
  rightPanelWidth: parseInt(ls.getItem(rightSidebarWidthKey), 10) || 500,
121
+ showAIConfigModal: false,
121
122
 
122
123
  // for settings related
123
124
  settingItem: initSettingItem([], settingMap.bookmarks),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.8.8",
3
+ "version": "3.8.15",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
@@ -1,14 +0,0 @@
1
- export default [
2
- {
3
- label: 'OpenAI',
4
- baseURL: 'https://api.openai.com/v1',
5
- homepage: 'https://openai.com',
6
- models: ['gpt-4', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k', 'gpt-4.5']
7
- },
8
- {
9
- label: 'DeepSeek',
10
- baseURL: 'https://api.deepseek.com/v1',
11
- homepage: 'https://deepseek.com',
12
- models: ['deepseek-chat', 'deepseek-coder', 'deepseek-reasoner']
13
- }
14
- ]