@electerm/electerm-react 5.5.56 → 5.5.76
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 +12 -0
- package/client/common/dangerous-session-fields.js +39 -0
- package/client/common/default-setting.js +1 -0
- package/client/common/distribute-tabs.js +42 -0
- package/client/common/import-task.js +46 -3
- package/client/common/parse-quick-connect.js +47 -6
- package/client/common/quick-connect-string.js +134 -0
- package/client/common/safe-join.js +33 -0
- package/client/common/sanitize-filename.js +8 -0
- package/client/common/ui-theme-color.mjs +136 -0
- package/client/common/update-check.js +14 -0
- package/client/components/ai/ai-output.jsx +5 -0
- package/client/components/ai/ai.styl +19 -0
- package/client/components/bookmark-form/copy-bookmark.jsx +83 -0
- package/client/components/bookmark-form/form-renderer.jsx +13 -1
- package/client/components/bookmark-form/index.jsx +20 -1
- package/client/components/bookmark-form/render-form.jsx +2 -2
- package/client/components/file-transfer/remote2remote-handlers.jsx +8 -3
- package/client/components/main/ui-theme.jsx +1 -20
- package/client/components/main/upgrade.jsx +2 -0
- package/client/components/setting-panel/install-command.jsx +151 -0
- package/client/components/setting-panel/setting-common.jsx +5 -0
- package/client/components/setting-panel/setting-terminal.jsx +10 -1
- package/client/components/terminal/mixins/term-attach.js +10 -2
- package/client/components/terminal/mixins/term-init.js +1 -0
- package/client/components/terminal/mixins/term-socket.js +12 -2
- package/client/components/terminal/mixins/term-suggestions.js +16 -3
- package/client/components/terminal/osc52-addon.js +7 -2
- package/client/components/terminal/session-status-overlay.jsx +44 -0
- package/client/components/terminal/shell.js +11 -3
- package/client/components/terminal/shortcut-bar-press.js +192 -0
- package/client/components/terminal/shortcut-bar.jsx +199 -4
- package/client/components/terminal/shortcut-bar.styl +40 -21
- package/client/components/terminal/ssh-reload-state.js +41 -2
- package/client/components/terminal/startup-queue.js +6 -2
- package/client/components/terminal/terminal.jsx +17 -2
- package/client/components/terminal/terminal.styl +14 -5
- package/client/store/mcp-handler.js +8 -15
- package/client/store/sync.js +47 -18
- package/client/store/tab.js +47 -17
- package/client/store/watch.js +8 -0
- package/package.json +1 -1
- package/client/components/terminal/reconnect-overlay.jsx +0 -14
|
@@ -17,23 +17,16 @@ import {
|
|
|
17
17
|
validateBookmarkData
|
|
18
18
|
} from '../components/bookmark-form/fix-bookmark-default'
|
|
19
19
|
import newTerm from '../common/new-terminal'
|
|
20
|
+
import dangerousSessionFields from '../common/dangerous-session-fields'
|
|
20
21
|
|
|
21
22
|
// Dangerous props that must not be accepted from MCP/AI tool calls.
|
|
22
|
-
//
|
|
23
|
-
// These allow arbitrary command execution if set
|
|
24
|
-
// override the shell binary, setEnv injects
|
|
25
|
-
// executes scripts, interactiveValues
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'execWindows',
|
|
30
|
-
'execWindowsArgs',
|
|
31
|
-
'execMacArgs',
|
|
32
|
-
'execLinuxArgs',
|
|
33
|
-
'setEnv',
|
|
34
|
-
'runScripts',
|
|
35
|
-
'interactiveValues'
|
|
36
|
-
]
|
|
23
|
+
// Shared with src/client/store/tab.js and the quick connect parser so the
|
|
24
|
+
// copies cannot drift apart. These allow arbitrary command execution if set
|
|
25
|
+
// (execLinux/execLinuxArgs override the shell binary, setEnv injects
|
|
26
|
+
// environment variables, runScripts executes scripts, interactiveValues
|
|
27
|
+
// injects interactive prompts, triggers auto-answer terminal output,
|
|
28
|
+
// proxyCommand is spawned before any SSH authentication).
|
|
29
|
+
const dangerousTabProps = dangerousSessionFields
|
|
37
30
|
|
|
38
31
|
// Tab/session UI state keys that must never be set by MCP/AI callers.
|
|
39
32
|
// A crafted `batch` (missing, string, or out-of-range) used to crash the
|
package/client/store/sync.js
CHANGED
|
@@ -742,29 +742,57 @@ export default (Store) => {
|
|
|
742
742
|
}
|
|
743
743
|
fixed[n] = arr
|
|
744
744
|
}
|
|
745
|
-
//
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
745
|
+
// Import replaces data sets, so the store has to end up holding exactly
|
|
746
|
+
// `fixed`. Keep a copy of what it holds now: if the import is cancelled or
|
|
747
|
+
// throws, this goes back, so the store and the watcher snapshots agree
|
|
748
|
+
// again and the watcher restart cannot be mistaken for a mass deletion.
|
|
749
|
+
const backup = new Map(
|
|
750
|
+
names.map(n => [n, copy(store.getItems(n))])
|
|
751
|
+
)
|
|
752
752
|
await runImportTask({
|
|
753
753
|
title: e('import'),
|
|
754
754
|
batch: 200,
|
|
755
755
|
stopWatchers: names,
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
756
|
+
// Clearing happens *inside* the task, i.e. while the watchers are
|
|
757
|
+
// stopped, rather than before it. Doing it earlier meant the empty store
|
|
758
|
+
// was already in place while the snapshots still described the full
|
|
759
|
+
// table, so any path that restarted a watcher without the import having
|
|
760
|
+
// finished (cancel, throw) deleted every record on disk.
|
|
761
|
+
steps: [
|
|
762
|
+
{
|
|
763
|
+
label: e('import'),
|
|
764
|
+
weight: 1,
|
|
765
|
+
run: () => {
|
|
766
|
+
action(() => {
|
|
767
|
+
for (const n of names) {
|
|
768
|
+
store.setItems(n, [])
|
|
769
|
+
}
|
|
770
|
+
})()
|
|
765
771
|
}
|
|
766
|
-
}
|
|
767
|
-
|
|
772
|
+
},
|
|
773
|
+
// one chunked step per data set (bookmarks, groups, themes, ...),
|
|
774
|
+
// so the progress bar advances per batch instead of per data set
|
|
775
|
+
...names.map((n) => {
|
|
776
|
+
const arr = fixed[n]
|
|
777
|
+
return {
|
|
778
|
+
label: n,
|
|
779
|
+
items: arr,
|
|
780
|
+
process: (chunk) => {
|
|
781
|
+
store[n].push(...chunk)
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
})
|
|
785
|
+
],
|
|
786
|
+
// The import did not complete: put the store back the way it was so the
|
|
787
|
+
// watcher restart below is a no-op instead of deleting the untouched
|
|
788
|
+
// records. (import-task.js also reseeds each watcher before restarting.)
|
|
789
|
+
onAbort: () => {
|
|
790
|
+
action(() => {
|
|
791
|
+
for (const [n, arr] of backup) {
|
|
792
|
+
store.setItems(n, copy(arr))
|
|
793
|
+
}
|
|
794
|
+
})()
|
|
795
|
+
}
|
|
768
796
|
})
|
|
769
797
|
store.updateConfig(stripServerManagedKeys(objs.config))
|
|
770
798
|
if (objs.config?.theme) {
|
|
@@ -819,6 +847,7 @@ export default (Store) => {
|
|
|
819
847
|
const configSyncKeys = [
|
|
820
848
|
'keepaliveInterval',
|
|
821
849
|
'rightClickSelectsWord',
|
|
850
|
+
'mouseEventsRequireAlt',
|
|
822
851
|
'pasteWhenContextMenu',
|
|
823
852
|
'ctrlOrMetaOpenTerminalLink',
|
|
824
853
|
'hotkey',
|
package/client/store/tab.js
CHANGED
|
@@ -18,6 +18,8 @@ import uid from '../common/uid'
|
|
|
18
18
|
import newTerm, { updateCount } from '../common/new-terminal.js'
|
|
19
19
|
import { action } from 'manate'
|
|
20
20
|
import { shouldCaptureTerminalReloadState } from '../components/terminal/ssh-reload-state.js'
|
|
21
|
+
import dangerousSessionFields from '../common/dangerous-session-fields'
|
|
22
|
+
import { distributeTabsEvenly } from '../common/distribute-tabs'
|
|
21
23
|
|
|
22
24
|
function captureSshSessionState (tab, config) {
|
|
23
25
|
return shouldCaptureTerminalReloadState(tab, config)
|
|
@@ -415,23 +417,9 @@ export default Store => {
|
|
|
415
417
|
store.updateHistory(newTab)
|
|
416
418
|
}
|
|
417
419
|
|
|
418
|
-
// Dangerous props that should not be accepted from IPC
|
|
419
|
-
const dangerousTabProps = [
|
|
420
|
-
'execLinux',
|
|
421
|
-
'execMac',
|
|
422
|
-
'execWindows',
|
|
423
|
-
'execWindowsArgs',
|
|
424
|
-
'execMacArgs',
|
|
425
|
-
'execLinuxArgs',
|
|
426
|
-
'setEnv',
|
|
427
|
-
'runScripts',
|
|
428
|
-
'interactiveValues',
|
|
429
|
-
'triggers'
|
|
430
|
-
]
|
|
431
|
-
|
|
432
420
|
Store.prototype.ipcOpenTab = function (parsed) {
|
|
433
421
|
const safeTab = Object.fromEntries(
|
|
434
|
-
Object.entries(parsed).filter(([key]) => !
|
|
422
|
+
Object.entries(parsed).filter(([key]) => !dangerousSessionFields.includes(key))
|
|
435
423
|
)
|
|
436
424
|
return window.store.addTab(safeTab)
|
|
437
425
|
}
|
|
@@ -504,12 +492,45 @@ export default Store => {
|
|
|
504
492
|
}
|
|
505
493
|
ntb.batch = (currentLayoutBatch + 1) % maxBatch
|
|
506
494
|
if (layout === 'c1') {
|
|
507
|
-
store.setLayout('c2')
|
|
495
|
+
store.setLayout('c2', false)
|
|
508
496
|
}
|
|
509
497
|
store.addTab(ntb)
|
|
510
498
|
}
|
|
511
499
|
|
|
512
|
-
|
|
500
|
+
// Spread tabs over the panes of a layout with more panes.
|
|
501
|
+
// Only used when leaving the single layout: once more than one pane is on
|
|
502
|
+
// screen the arrangement is the user's, and is left alone.
|
|
503
|
+
Store.prototype.distributeTabs = function (newCount) {
|
|
504
|
+
const { store } = window
|
|
505
|
+
const { tabs } = store
|
|
506
|
+
if (tabs.length < 2) {
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
// contiguous chunks keep the tab order: [A B C D E] -> [A B] [C D] [E]
|
|
510
|
+
const batches = distributeTabsEvenly(tabs.length, newCount)
|
|
511
|
+
tabs.forEach((t, i) => {
|
|
512
|
+
t.batch = batches[i]
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// make sure every pane has a valid active tab
|
|
517
|
+
Store.prototype.fixActiveTabIds = function (count) {
|
|
518
|
+
const { store } = window
|
|
519
|
+
for (let b = 0; b < count; b++) {
|
|
520
|
+
const inBatch = store.tabs.filter(t => t.batch === b)
|
|
521
|
+
const key = `activeTabId${b}`
|
|
522
|
+
if (!inBatch.some(t => t.id === store[key])) {
|
|
523
|
+
store[key] = inBatch.length ? inBatch[0].id : ''
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const active = store.tabs.find(t => t.id === store.activeTabId)
|
|
527
|
+
if (active) {
|
|
528
|
+
store[`activeTabId${active.batch}`] = active.id
|
|
529
|
+
store.currentLayoutBatch = active.batch
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
Store.prototype.setLayout = function (layout, redistribute = true) {
|
|
513
534
|
const { store } = window
|
|
514
535
|
const prevLayout = store.layout
|
|
515
536
|
const { activeTabId } = store
|
|
@@ -546,6 +567,15 @@ export default Store => {
|
|
|
546
567
|
if (store.currentLayoutBatch >= newBatchCount) {
|
|
547
568
|
store.currentLayoutBatch = newBatchCount - 1
|
|
548
569
|
}
|
|
570
|
+
} else if (
|
|
571
|
+
redistribute &&
|
|
572
|
+
store.config.autoDistributeTabsWhenLayoutChange &&
|
|
573
|
+
prevBatchCount === 1 &&
|
|
574
|
+
newBatchCount > 1
|
|
575
|
+
) {
|
|
576
|
+
// leaving the single layout: hand the tabs out evenly
|
|
577
|
+
store.distributeTabs(newBatchCount)
|
|
578
|
+
store.fixActiveTabIds(newBatchCount)
|
|
549
579
|
}
|
|
550
580
|
store.focus()
|
|
551
581
|
}
|
package/client/store/watch.js
CHANGED
|
@@ -82,6 +82,14 @@ export default store => {
|
|
|
82
82
|
}
|
|
83
83
|
return store[name]
|
|
84
84
|
}, schedule)
|
|
85
|
+
// Make the current store contents the watcher's baseline, so a later
|
|
86
|
+
// start() diffs against the store rather than a snapshot that no longer
|
|
87
|
+
// describes it. Needed after a rolled-back import: the store has just been
|
|
88
|
+
// restored to its pre-import state, and diffing that against the snapshot
|
|
89
|
+
// taken before the import would be read as "every record was removed".
|
|
90
|
+
window[`watch${name}`].reseed = () => {
|
|
91
|
+
refsStatic.add('oldState-' + name, deepCopy(store.getItems(name)) || [])
|
|
92
|
+
}
|
|
85
93
|
window[`watch${name}`].start()
|
|
86
94
|
}
|
|
87
95
|
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { memo } from 'react'
|
|
2
|
-
|
|
3
|
-
const e = window.translate
|
|
4
|
-
|
|
5
|
-
export default memo(function ReconnectOverlay ({ countdown }) {
|
|
6
|
-
if (countdown === null || countdown === undefined) {
|
|
7
|
-
return null
|
|
8
|
-
}
|
|
9
|
-
return (
|
|
10
|
-
<div className='terminal-reconnect-overlay'>
|
|
11
|
-
{e('autoReconnectTerminal')}: {countdown}s
|
|
12
|
-
</div>
|
|
13
|
-
)
|
|
14
|
-
})
|