@electerm/electerm-react 5.3.26 → 5.5.9
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/README.md +246 -12
- package/client/common/choose-save-folder.js +21 -2
- package/client/common/constants.js +20 -6
- package/client/common/create-title.jsx +6 -0
- package/client/common/default-setting.js +15 -0
- package/client/common/init-setting-item.js +5 -0
- package/client/common/platform.js +24 -0
- package/client/components/ai/agent-tool-call-card.jsx +10 -2
- package/client/components/ai/agent.js +4 -1
- package/client/components/ai/ai-attachments.js +87 -0
- package/client/components/ai/ai-chat-empty.jsx +43 -0
- package/client/components/ai/ai-chat-entry.jsx +13 -4
- package/client/components/ai/ai-chat-history-item.jsx +28 -4
- package/client/components/ai/ai-chat-history.jsx +2 -1
- package/client/components/ai/ai-chat.jsx +104 -13
- package/client/components/ai/ai-config-props.js +4 -130
- package/client/components/ai/ai-config.jsx +135 -15
- package/client/components/ai/ai-presets.js +173 -0
- package/client/components/ai/ai.styl +83 -0
- package/client/components/bg/css-overwrite.jsx +11 -0
- package/client/components/bookmark-form/bookmark-schema.js +4 -0
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/config/common-fields.js +12 -0
- package/client/components/bookmark-form/config/local.js +3 -2
- package/client/components/bookmark-form/config/serial.js +3 -2
- package/client/components/bookmark-form/config/ssh.js +2 -1
- package/client/components/bookmark-form/config/telnet.js +3 -2
- package/client/components/common/item-filter.jsx +76 -0
- package/client/components/common/item-filter.styl +54 -0
- package/client/components/common/lazy-boundary.jsx +34 -0
- package/client/components/common/opacity.jsx +3 -1
- package/client/components/footer/cmd-history.jsx +132 -45
- package/client/components/footer/cmd-history.styl +42 -4
- package/client/components/footer/footer-entry.jsx +35 -5
- package/client/components/footer/footer.styl +8 -1
- package/client/components/footer/multi-tab-run-modal.jsx +83 -0
- package/client/components/footer/tab-select.jsx +34 -17
- package/client/components/layout/layout.jsx +23 -31
- package/client/components/layout/pixed.js +3 -0
- package/client/components/main/main.jsx +55 -28
- package/client/components/main/term-fullscreen.styl +9 -3
- package/client/components/quick-commands/qm.styl +3 -2
- package/client/components/quick-commands/quick-command-create-modal.jsx +60 -0
- package/client/components/quick-commands/quick-commands-form-elem.jsx +10 -4
- package/client/components/rdp/file-transfer.js +4 -4
- package/client/components/remote-monitor/monitor-details.jsx +458 -0
- package/client/components/remote-monitor/monitor-details.styl +87 -0
- package/client/components/remote-monitor/monitor-model.js +764 -0
- package/client/components/remote-monitor/remote-monitor-bar-entry.jsx +18 -0
- package/client/components/remote-monitor/remote-monitor-bar.jsx +344 -0
- package/client/components/remote-monitor/remote-monitor-bar.styl +106 -0
- package/client/components/remote-monitor/session-monitor.js +361 -0
- package/client/components/remote-monitor/use-monitor-details.js +10 -0
- package/client/components/remote-monitor/visibility.js +18 -0
- package/client/components/session/session-control.jsx +29 -2
- package/client/components/session/session.jsx +40 -18
- package/client/components/session/sessions.jsx +5 -2
- package/client/components/setting-panel/setting-common.jsx +25 -56
- package/client/components/setting-panel/setting-modal.jsx +57 -42
- package/client/components/setting-panel/setting-terminal.jsx +8 -2
- package/client/components/setting-panel/setting.styl +1 -1
- package/client/components/setting-panel/tab-triggers.jsx +71 -0
- package/client/components/sftp/file-compare-modal.jsx +6 -3
- package/client/components/sftp/file-item.jsx +5 -3
- package/client/components/sftp/owner-list.js +11 -2
- package/client/components/sftp/sftp-entry.jsx +4 -0
- package/client/components/side-panel-r/side-panel-r.jsx +2 -2
- package/client/components/sidebar/side-panel.jsx +1 -1
- package/client/components/sys-menu/menu-btn.jsx +30 -1
- package/client/components/tabs/index.jsx +2 -2
- package/client/components/tabs/tab-title.jsx +3 -2
- package/client/components/tabs/tab.jsx +5 -1
- package/client/components/terminal/attach-addon-custom.js +295 -49
- package/client/components/terminal/automation/index.js +64 -0
- package/client/components/terminal/automation/keys.js +49 -0
- package/client/components/terminal/automation/stream-matcher.js +82 -0
- package/client/components/terminal/automation/strip-ansi.js +19 -0
- package/client/components/terminal/automation/trigger-engine.js +233 -0
- package/client/components/terminal/automation/trigger-presets.js +110 -0
- package/client/components/terminal/command-tracker-addon.js +31 -1
- package/client/components/terminal/mixins/index.js +27 -0
- package/client/components/terminal/mixins/term-attach.js +123 -0
- package/client/components/terminal/mixins/term-config.jsx +84 -0
- package/client/components/terminal/mixins/term-content.js +126 -0
- package/client/components/terminal/mixins/term-context-menu.jsx +323 -0
- package/client/components/terminal/mixins/term-file-drop.js +149 -0
- package/client/components/terminal/mixins/term-init.js +122 -0
- package/client/components/terminal/mixins/term-log.jsx +96 -0
- package/client/components/terminal/mixins/term-resize.js +87 -0
- package/client/components/terminal/mixins/term-socket.js +433 -0
- package/client/components/terminal/mixins/term-suggestions.js +224 -0
- package/client/components/terminal/mixins/term-theme.js +190 -0
- package/client/components/terminal/mixins/term-touch.js +523 -0
- package/client/components/terminal/shell-detect.js +37 -0
- package/client/components/terminal/shell.js +0 -28
- package/client/components/terminal/shortcut-bar-defs.js +30 -4
- package/client/components/terminal/shortcut-bar-edit.jsx +40 -9
- package/client/components/terminal/shortcut-bar-entry.jsx +6 -3
- package/client/components/terminal/shortcut-bar.jsx +17 -10
- package/client/components/terminal/shortcut-bar.styl +4 -0
- package/client/components/terminal/ssh-reload-state.js +96 -0
- package/client/components/terminal/startup-queue.js +433 -0
- package/client/components/terminal/terminal-interactive.jsx +32 -0
- package/client/components/terminal/terminal-select-text.jsx +69 -0
- package/client/components/terminal/terminal.jsx +227 -2064
- package/client/components/terminal/terminal.styl +61 -0
- package/client/components/terminal/transfer-client-base.js +5 -29
- package/client/components/terminal/trzsz-client.js +1 -3
- package/client/components/terminal/xmodem-client.js +1 -3
- package/client/components/terminal/xterm-loader.js +7 -0
- package/client/components/terminal/zmodem-client.js +1 -3
- package/client/components/terminal-info/base.jsx +18 -70
- package/client/components/terminal-info/terminal-info-entry.jsx +13 -5
- package/client/components/terminal-info/terminal-info.jsx +50 -59
- package/client/components/terminal-info/terminal-info.styl +6 -49
- package/client/components/text-editor/open-text-editor.js +30 -0
- package/client/components/text-editor/text-editor-entry.jsx +17 -6
- package/client/components/text-editor/text-editor.jsx +14 -0
- package/client/components/triggers/trigger-editor.jsx +246 -0
- package/client/components/triggers/trigger-form.jsx +200 -0
- package/client/components/triggers/trigger-session-modal.jsx +199 -0
- package/client/components/triggers/trigger-setting-list.jsx +89 -0
- package/client/components/triggers/trigger-transport.jsx +60 -0
- package/client/components/triggers/triggers-field.jsx +19 -0
- package/client/components/widgets/widgets-list.jsx +11 -1
- package/client/css/mobile.styl +29 -8
- package/client/store/common.js +12 -1
- package/client/store/init-state.js +7 -0
- package/client/store/mcp-handler.js +158 -30
- package/client/store/setting.js +9 -0
- package/client/store/store.js +2 -0
- package/client/store/sync.js +2 -0
- package/client/store/tab.js +42 -3
- package/client/store/trigger.js +118 -0
- package/client/store/watch.js +13 -5
- package/client/views/index.pug +3 -0
- package/package.json +1 -1
- package/client/components/terminal-info/activity.jsx +0 -57
- package/client/components/terminal-info/data-cols-parser.jsx +0 -54
- package/client/components/terminal-info/disk.jsx +0 -45
- package/client/components/terminal-info/format-disks.js +0 -45
- package/client/components/terminal-info/network.jsx +0 -121
- package/client/components/terminal-info/resource.jsx +0 -99
- package/client/components/terminal-info/run-cmd.jsx +0 -263
- package/client/components/terminal-info/sys-info.jsx +0 -56
- package/client/components/terminal-info/up.jsx +0 -17
package/client/store/store.js
CHANGED
|
@@ -13,6 +13,7 @@ import bookmarkExtend from './bookmark'
|
|
|
13
13
|
import commonExtend from './common'
|
|
14
14
|
import itemExtend from './item'
|
|
15
15
|
import quickCommandExtend from './quick-command'
|
|
16
|
+
import triggerExtend from './trigger'
|
|
16
17
|
import sessionExtend from './session'
|
|
17
18
|
import settingExtend from './setting'
|
|
18
19
|
import sidebarExtend from './sidebar'
|
|
@@ -312,6 +313,7 @@ bookmarkExtend(Store)
|
|
|
312
313
|
commonExtend(Store)
|
|
313
314
|
itemExtend(Store)
|
|
314
315
|
quickCommandExtend(Store)
|
|
316
|
+
triggerExtend(Store)
|
|
315
317
|
sessionExtend(Store)
|
|
316
318
|
settingExtend(Store)
|
|
317
319
|
sidebarExtend(Store)
|
package/client/store/sync.js
CHANGED
package/client/store/tab.js
CHANGED
|
@@ -17,6 +17,13 @@ import generate from '../common/id-with-stamp'
|
|
|
17
17
|
import uid from '../common/uid'
|
|
18
18
|
import newTerm, { updateCount } from '../common/new-terminal.js'
|
|
19
19
|
import { action } from 'manate'
|
|
20
|
+
import { shouldCaptureTerminalReloadState } from '../components/terminal/ssh-reload-state.js'
|
|
21
|
+
|
|
22
|
+
function captureSshSessionState (tab, config) {
|
|
23
|
+
return shouldCaptureTerminalReloadState(tab, config)
|
|
24
|
+
? refs.get(`term-${tab.id}`)?.getReloadState?.()
|
|
25
|
+
: undefined
|
|
26
|
+
}
|
|
20
27
|
|
|
21
28
|
export default Store => {
|
|
22
29
|
Store.prototype.nextTabCount = function () {
|
|
@@ -96,6 +103,11 @@ export default Store => {
|
|
|
96
103
|
const oldTab = tabs[index]
|
|
97
104
|
const oldBatch = oldTab.batch
|
|
98
105
|
|
|
106
|
+
// Reload state is captured only for a shell terminal refresh/reconnect
|
|
107
|
+
// (ssh, local, telnet, serial). Closing a tab goes through removeTabs
|
|
108
|
+
// and never reaches this code path.
|
|
109
|
+
const reloadState = captureSshSessionState(oldTab, store.config)
|
|
110
|
+
|
|
99
111
|
// Create copy of old tab with new ID
|
|
100
112
|
const newTab = {
|
|
101
113
|
...oldTab,
|
|
@@ -103,6 +115,10 @@ export default Store => {
|
|
|
103
115
|
id: generate(), // Need to create new ID
|
|
104
116
|
status: statusMap.processing // Reset status
|
|
105
117
|
}
|
|
118
|
+
delete newTab._reloadState
|
|
119
|
+
if (reloadState) {
|
|
120
|
+
newTab._reloadState = reloadState
|
|
121
|
+
}
|
|
106
122
|
|
|
107
123
|
// Add new tab at next index
|
|
108
124
|
tabs.splice(index + 1, 0, newTab)
|
|
@@ -153,6 +169,7 @@ export default Store => {
|
|
|
153
169
|
}
|
|
154
170
|
|
|
155
171
|
const sourceTab = tabs[targetIndex]
|
|
172
|
+
const sessionState = captureSshSessionState(sourceTab, store.config)
|
|
156
173
|
const duplicatedTab = {
|
|
157
174
|
...deepCopy(sourceTab),
|
|
158
175
|
id: generate(),
|
|
@@ -160,6 +177,10 @@ export default Store => {
|
|
|
160
177
|
status: statusMap.processing,
|
|
161
178
|
isTransporting: undefined
|
|
162
179
|
}
|
|
180
|
+
delete duplicatedTab._reloadState
|
|
181
|
+
if (sessionState) {
|
|
182
|
+
duplicatedTab._reloadState = sessionState
|
|
183
|
+
}
|
|
163
184
|
|
|
164
185
|
// Insert the duplicated tab after the source tab
|
|
165
186
|
tabs.splice(targetIndex + 1, 0, duplicatedTab)
|
|
@@ -361,7 +382,19 @@ export default Store => {
|
|
|
361
382
|
const { store } = window
|
|
362
383
|
const { tabs } = store
|
|
363
384
|
newTab.tabCount = store.nextTabCount()
|
|
364
|
-
|
|
385
|
+
// Sanitize batch: MCP/AI callers may pass a missing, string, or
|
|
386
|
+
// out-of-range batch which would crash Sessions (sizes[batch]
|
|
387
|
+
// undefined). Clamp to a valid pane index for the current layout.
|
|
388
|
+
let batchNum = batch ?? newTab.batch ?? window.openTabBatch ?? window.store.currentLayoutBatch
|
|
389
|
+
batchNum = Number(batchNum)
|
|
390
|
+
const maxBatch = (splitConfig[store.layout] && splitConfig[store.layout].children) || 1
|
|
391
|
+
if (!Number.isInteger(batchNum) || batchNum < 0 || batchNum >= maxBatch) {
|
|
392
|
+
batchNum = Number(window.store.currentLayoutBatch) || 0
|
|
393
|
+
if (!Number.isInteger(batchNum) || batchNum < 0 || batchNum >= maxBatch) {
|
|
394
|
+
batchNum = 0
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
newTab.batch = batchNum
|
|
365
398
|
if (!newTab.id) {
|
|
366
399
|
newTab.id = generate()
|
|
367
400
|
}
|
|
@@ -376,7 +409,6 @@ export default Store => {
|
|
|
376
409
|
} else {
|
|
377
410
|
tabs.push(newTab)
|
|
378
411
|
}
|
|
379
|
-
const batchNum = newTab.batch
|
|
380
412
|
store[`activeTabId${batchNum}`] = newTab.id
|
|
381
413
|
store.activeTabId = newTab.id
|
|
382
414
|
store.currentLayoutBatch = batchNum
|
|
@@ -393,7 +425,8 @@ export default Store => {
|
|
|
393
425
|
'execLinuxArgs',
|
|
394
426
|
'setEnv',
|
|
395
427
|
'runScripts',
|
|
396
|
-
'interactiveValues'
|
|
428
|
+
'interactiveValues',
|
|
429
|
+
'triggers'
|
|
397
430
|
]
|
|
398
431
|
|
|
399
432
|
Store.prototype.ipcOpenTab = function (parsed) {
|
|
@@ -454,12 +487,17 @@ export default Store => {
|
|
|
454
487
|
const defaultStatus = statusMap.processing
|
|
455
488
|
const { layout, currentLayoutBatch } = store
|
|
456
489
|
const ntb = deepCopy(tab)
|
|
490
|
+
const sessionState = captureSshSessionState(tab, store.config)
|
|
457
491
|
Object.assign(ntb, {
|
|
458
492
|
id: generate(),
|
|
459
493
|
status: defaultStatus,
|
|
460
494
|
isTransporting: undefined,
|
|
461
495
|
pane: paneMap.terminal
|
|
462
496
|
})
|
|
497
|
+
delete ntb._reloadState
|
|
498
|
+
if (sessionState) {
|
|
499
|
+
ntb._reloadState = sessionState
|
|
500
|
+
}
|
|
463
501
|
let maxBatch = splitConfig[layout].children
|
|
464
502
|
if (maxBatch < 2) {
|
|
465
503
|
maxBatch = 2
|
|
@@ -548,6 +586,7 @@ export default Store => {
|
|
|
548
586
|
'sshTunnelResults',
|
|
549
587
|
'displayRaw',
|
|
550
588
|
'autoReConnect',
|
|
589
|
+
'_reloadState',
|
|
551
590
|
'isPinned'
|
|
552
591
|
]
|
|
553
592
|
const { history } = store
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* declarative auto triggers (ZocRespond style) related functions
|
|
3
|
+
* global triggers live in store.triggers (persisted via db watch),
|
|
4
|
+
* bookmark triggers live on bookmark/tab `triggers` field
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { settingMap } from '../common/constants'
|
|
8
|
+
import { refs } from '../components/common/ref'
|
|
9
|
+
import { validateTriggers } from '../components/terminal/automation/trigger-engine.js'
|
|
10
|
+
|
|
11
|
+
export function normalizeTrigger (t = {}) {
|
|
12
|
+
return {
|
|
13
|
+
id: t.id || '',
|
|
14
|
+
name: t.name || '',
|
|
15
|
+
enabled: t.enabled !== false,
|
|
16
|
+
match: {
|
|
17
|
+
type: t.match?.type === 'regex' ? 'regex' : 'text',
|
|
18
|
+
value: t.match?.value || '',
|
|
19
|
+
caseSensitive: !!t.match?.caseSensitive
|
|
20
|
+
},
|
|
21
|
+
action: {
|
|
22
|
+
type: t.action?.type === 'notify' ? 'notify' : 'send',
|
|
23
|
+
value: t.action?.value || ''
|
|
24
|
+
},
|
|
25
|
+
sendEnter: t.sendEnter !== false,
|
|
26
|
+
mode: ['repeat', 'once', 'cooldown'].includes(t.mode) ? t.mode : 'cooldown',
|
|
27
|
+
cooldownMs: Number.isFinite(+t.cooldownMs) ? +t.cooldownMs : 500
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default Store => {
|
|
32
|
+
Store.prototype.addTrigger = function (trigger) {
|
|
33
|
+
window.store.addItem(normalizeTrigger(trigger), settingMap.triggers)
|
|
34
|
+
window.store.refreshAllTriggers()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Store.prototype.editTrigger = function (id, update) {
|
|
38
|
+
window.store.editItem(id, normalizeTrigger({ ...window.store.triggers.find(t => t.id === id), ...update }), settingMap.triggers)
|
|
39
|
+
window.store.refreshAllTriggers()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Store.prototype.delTrigger = function ({ id }) {
|
|
43
|
+
window.store.delItem({ id }, settingMap.triggers)
|
|
44
|
+
window.store.refreshAllTriggers()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Store.prototype.setTriggers = function (list) {
|
|
48
|
+
const arr = (list || []).map(normalizeTrigger)
|
|
49
|
+
const errors = validateTriggers(arr)
|
|
50
|
+
if (errors.length) {
|
|
51
|
+
return errors
|
|
52
|
+
}
|
|
53
|
+
window.store.setItems(settingMap.triggers, arr)
|
|
54
|
+
window.store.refreshAllTriggers()
|
|
55
|
+
return []
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// effective triggers for a tab: predefined (global) triggers resolved with
|
|
59
|
+
// per-session on/off overrides + tab session/bookmark triggers.
|
|
60
|
+
// Only predefined triggers persist (db, synced); bookmark triggers stay on
|
|
61
|
+
// the bookmark record; session temp rules live on the in-memory tab only.
|
|
62
|
+
Store.prototype.getEffectiveTriggers = function (tab) {
|
|
63
|
+
const overrides = (tab && tab.triggerOverrides) || {}
|
|
64
|
+
const globals = (window.store.triggers || [])
|
|
65
|
+
.filter(t => t && t.match && t.match.value)
|
|
66
|
+
.map(t => {
|
|
67
|
+
const on = t.id in overrides ? overrides[t.id] : t.enabled !== false
|
|
68
|
+
return normalizeTrigger({ ...t, enabled: on })
|
|
69
|
+
})
|
|
70
|
+
const session = (tab && tab.triggers) || []
|
|
71
|
+
return [...globals, ...session.map(normalizeTrigger)]
|
|
72
|
+
.filter(t => t.enabled !== false && t.match.value)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// per-session on/off for a predefined trigger (memory only, not persisted)
|
|
76
|
+
Store.prototype.togglePredefinedTrigger = function (tabId, triggerId, enabled) {
|
|
77
|
+
const id = tabId || window.store.activeTabId
|
|
78
|
+
const tab = window.store.tabs.find(t => t.id === id)
|
|
79
|
+
if (!tab) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
const overrides = { ...(tab.triggerOverrides || {}) }
|
|
83
|
+
overrides[triggerId] = !!enabled
|
|
84
|
+
window.store.updateTab(id, { triggerOverrides: overrides })
|
|
85
|
+
refs.get('term-' + id)?.refreshTriggers?.()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Store.prototype.getSessionTriggers = function (tabId) {
|
|
89
|
+
const tab = window.store.tabs.find(t => t.id === (tabId || window.store.activeTabId))
|
|
90
|
+
return (tab && tab.triggers) || []
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Store.prototype.setSessionTriggers = function (tabId, list) {
|
|
94
|
+
const id = tabId || window.store.activeTabId
|
|
95
|
+
const arr = (list || []).map(normalizeTrigger)
|
|
96
|
+
const errors = validateTriggers(arr.filter(t => t.match.value || t.name))
|
|
97
|
+
if (errors.length) {
|
|
98
|
+
return errors
|
|
99
|
+
}
|
|
100
|
+
window.store.updateTab(id, { triggers: arr })
|
|
101
|
+
refs.get('term-' + id)?.refreshTriggers?.()
|
|
102
|
+
return []
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Store.prototype.refreshAllTriggers = function () {
|
|
106
|
+
for (const tab of window.store.tabs || []) {
|
|
107
|
+
try {
|
|
108
|
+
refs.get('term-' + tab.id)?.refreshTriggers?.()
|
|
109
|
+
} catch (e) {
|
|
110
|
+
console.debug(e)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
Store.prototype.toggleTriggerSessionModal = function (open = null) {
|
|
116
|
+
window.store.triggerSessionOpen = open == null ? !window.store.triggerSessionOpen : !!open
|
|
117
|
+
}
|
|
118
|
+
}
|
package/client/store/watch.js
CHANGED
|
@@ -21,6 +21,12 @@ import dataCompare from '../common/data-compare'
|
|
|
21
21
|
|
|
22
22
|
export default store => {
|
|
23
23
|
for (const name of dbNamesForWatch) {
|
|
24
|
+
// ai chat history changes on every streaming tick (200ms polls,
|
|
25
|
+
// per-chunk agent updates); debounce to coalesce them into one
|
|
26
|
+
// db write after the stream pauses
|
|
27
|
+
const schedule = name === 'aiChatHistory'
|
|
28
|
+
? func => debounce(func, 1000)
|
|
29
|
+
: undefined
|
|
24
30
|
window[`watch${name}`] = autoRun(async () => {
|
|
25
31
|
const n = store.getItems(name)
|
|
26
32
|
if (window.migrating) {
|
|
@@ -51,10 +57,12 @@ export default store => {
|
|
|
51
57
|
...updated.map(item => update(item.id, item, name, false)),
|
|
52
58
|
added.length ? insert(name, added) : Promise.resolve()
|
|
53
59
|
])
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
if (orderChanged) {
|
|
61
|
+
await update(
|
|
62
|
+
`${name}:order`,
|
|
63
|
+
newOrder
|
|
64
|
+
)
|
|
65
|
+
}
|
|
58
66
|
if (name === 'bookmarks') {
|
|
59
67
|
store.bookmarksMap = new Map(
|
|
60
68
|
n.map(d => [d.id, d])
|
|
@@ -73,7 +81,7 @@ export default store => {
|
|
|
73
81
|
}
|
|
74
82
|
}
|
|
75
83
|
return store[name]
|
|
76
|
-
})
|
|
84
|
+
}, schedule)
|
|
77
85
|
window[`watch${name}`].start()
|
|
78
86
|
}
|
|
79
87
|
|
package/client/views/index.pug
CHANGED
|
@@ -23,6 +23,9 @@ html
|
|
|
23
23
|
}
|
|
24
24
|
.electerm-logo-bg {
|
|
25
25
|
background: transparent 50% 50% no-repeat url("./images/electerm-watermark.png");
|
|
26
|
+
/* the watermark is 766 x 266; without a size it is cropped on narrow
|
|
27
|
+
panes (mobile), so scale it down to fit the pane width instead */
|
|
28
|
+
background-size: min(100%, 766px) auto;
|
|
26
29
|
}
|
|
27
30
|
.morph-shape {
|
|
28
31
|
background: linear-gradient(45deg, #08c 0%, #09c 100%);
|
package/package.json
CHANGED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* process cpu/mem activities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Table, Tooltip, Popconfirm } from 'antd'
|
|
6
|
-
import { isEmpty } from 'lodash-es'
|
|
7
|
-
import { CloseCircleOutlined, BarsOutlined } from '@ant-design/icons'
|
|
8
|
-
import colsParser from './data-cols-parser'
|
|
9
|
-
|
|
10
|
-
const e = window.translate
|
|
11
|
-
|
|
12
|
-
export default function TerminalInfoActivities (props) {
|
|
13
|
-
const { activities, isRemote, terminalInfos } = props
|
|
14
|
-
if (isEmpty(activities) || !isRemote || !terminalInfos.includes('activities')) {
|
|
15
|
-
return null
|
|
16
|
-
}
|
|
17
|
-
const col = colsParser(activities[0])
|
|
18
|
-
col.unshift({
|
|
19
|
-
dataIndex: 'kill',
|
|
20
|
-
key: 'kill',
|
|
21
|
-
title: e('close'),
|
|
22
|
-
render: (txt, inst) => {
|
|
23
|
-
return (
|
|
24
|
-
<Tooltip
|
|
25
|
-
title={e('close')}
|
|
26
|
-
>
|
|
27
|
-
<Popconfirm
|
|
28
|
-
title={e('close') + ' pid:' + inst.pid + ' ?'}
|
|
29
|
-
onConfirm={() => props.killProcess(inst.pid)}
|
|
30
|
-
>
|
|
31
|
-
<CloseCircleOutlined
|
|
32
|
-
className='pointer'
|
|
33
|
-
/>
|
|
34
|
-
</Popconfirm>
|
|
35
|
-
</Tooltip>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
const ps = {
|
|
40
|
-
rowKey: 'pid',
|
|
41
|
-
dataSource: activities,
|
|
42
|
-
bordered: true,
|
|
43
|
-
columns: col,
|
|
44
|
-
size: 'small',
|
|
45
|
-
pagination: false,
|
|
46
|
-
scroll: {
|
|
47
|
-
y: 180,
|
|
48
|
-
x: 'max-content'
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return (
|
|
52
|
-
<div className='terminal-info-section terminal-info-act'>
|
|
53
|
-
<div className='pd1y bold'><BarsOutlined /> Activities</div>
|
|
54
|
-
<Table {...ps} />
|
|
55
|
-
</div>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { copy } from '../../common/clipboard'
|
|
2
|
-
import filesizeParser from 'filesize-parser'
|
|
3
|
-
import { filesize } from 'filesize'
|
|
4
|
-
|
|
5
|
-
const valueParserMaps = {
|
|
6
|
-
size: v => v,
|
|
7
|
-
used: filesizeParser,
|
|
8
|
-
avail: filesizeParser,
|
|
9
|
-
usedPercent: v => parseFloat(v.replace('%', ''))
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function valueParse (obj, k) {
|
|
13
|
-
if (valueParserMaps[k]) {
|
|
14
|
-
return valueParserMaps[k](obj[k])
|
|
15
|
-
}
|
|
16
|
-
return obj[k]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function copyValue (event) {
|
|
20
|
-
copy(event.target.getAttribute('data-content'))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default (data) => {
|
|
24
|
-
return Object.keys(data).map(k => {
|
|
25
|
-
const rd = (txt) => {
|
|
26
|
-
// txt is in KB, convert to bytes for filesize function
|
|
27
|
-
const parsed = parseInt(txt, 10)
|
|
28
|
-
const r = k === 'mem' ? filesize(Number.isFinite(parsed) ? parsed * 1024 : 0) : txt
|
|
29
|
-
const itemProps = {
|
|
30
|
-
className: 'activity-item pointer',
|
|
31
|
-
'data-content': r,
|
|
32
|
-
onClick: copyValue
|
|
33
|
-
}
|
|
34
|
-
return (
|
|
35
|
-
<div
|
|
36
|
-
{...itemProps}
|
|
37
|
-
>
|
|
38
|
-
{r}
|
|
39
|
-
</div>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
title: k,
|
|
44
|
-
dataIndex: k,
|
|
45
|
-
key: k,
|
|
46
|
-
sorter: (a, b) => {
|
|
47
|
-
const va = valueParse(a, k)
|
|
48
|
-
const vb = valueParse(b, k)
|
|
49
|
-
return va > vb ? 1 : -1
|
|
50
|
-
},
|
|
51
|
-
render: rd
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* disk info
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Table } from 'antd'
|
|
6
|
-
import { isEmpty } from 'lodash-es'
|
|
7
|
-
import colsParser from './data-cols-parser'
|
|
8
|
-
import { PartitionOutlined } from '@ant-design/icons'
|
|
9
|
-
|
|
10
|
-
export default function TerminalInfoDisk (props) {
|
|
11
|
-
const { disks, isRemote, terminalInfos } = props
|
|
12
|
-
if (isEmpty(disks) || !isRemote || !terminalInfos.includes('disks')) {
|
|
13
|
-
return null
|
|
14
|
-
}
|
|
15
|
-
const col = colsParser(disks[0])
|
|
16
|
-
disks.sort((a, b) => {
|
|
17
|
-
const af = a.filesystem || ''
|
|
18
|
-
const bf = b.filesystem || ''
|
|
19
|
-
if (af.startsWith('/') && !bf.startsWith('/')) {
|
|
20
|
-
return -1
|
|
21
|
-
}
|
|
22
|
-
if (!af.startsWith('/') && bf.startsWith('/')) {
|
|
23
|
-
return 1
|
|
24
|
-
}
|
|
25
|
-
return 0
|
|
26
|
-
})
|
|
27
|
-
const ps = {
|
|
28
|
-
rowKey: (rec) => `${rec.mount}_${rec.filesystem}`,
|
|
29
|
-
dataSource: disks,
|
|
30
|
-
bordered: true,
|
|
31
|
-
columns: col,
|
|
32
|
-
size: 'small',
|
|
33
|
-
pagination: false,
|
|
34
|
-
scroll: {
|
|
35
|
-
y: 180,
|
|
36
|
-
x: 'max-content'
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return (
|
|
40
|
-
<div className='terminal-info-section terminal-info-disk'>
|
|
41
|
-
<div className='pd1y bold'><PartitionOutlined /> File system</div>
|
|
42
|
-
<Table {...ps} />
|
|
43
|
-
</div>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* parse `df` output into disk list
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export default function formatDisks (str) {
|
|
6
|
-
if (!str) {
|
|
7
|
-
return {
|
|
8
|
-
disks: []
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
const r = str.split('\n')
|
|
12
|
-
.slice(1)
|
|
13
|
-
.reduce((acc, s) => {
|
|
14
|
-
if (!s.trim()) {
|
|
15
|
-
return acc
|
|
16
|
-
}
|
|
17
|
-
const arr = s.trim().split(/ +/)
|
|
18
|
-
// df wraps long filesystem names (e.g. nfs "host:/very/long/export/path")
|
|
19
|
-
// onto a second line that starts with spaces and only holds the numeric
|
|
20
|
-
// columns. Merge such continuation lines back into the previous entry, so
|
|
21
|
-
// those disks (commonly nfs) are listed instead of dropped.
|
|
22
|
-
if (/^\s/.test(s) && acc.length) {
|
|
23
|
-
const prev = acc[acc.length - 1]
|
|
24
|
-
prev.size = arr[0]
|
|
25
|
-
prev.used = arr[1]
|
|
26
|
-
prev.avail = arr[2]
|
|
27
|
-
prev.usedPercent = arr[3]
|
|
28
|
-
prev.mount = arr[4]
|
|
29
|
-
return acc
|
|
30
|
-
}
|
|
31
|
-
acc.push({
|
|
32
|
-
filesystem: arr[0],
|
|
33
|
-
size: arr[1],
|
|
34
|
-
used: arr[2],
|
|
35
|
-
avail: arr[3],
|
|
36
|
-
usedPercent: arr[4],
|
|
37
|
-
mount: arr[5]
|
|
38
|
-
})
|
|
39
|
-
return acc
|
|
40
|
-
}, [])
|
|
41
|
-
.filter(d => d.filesystem && d.mount)
|
|
42
|
-
return {
|
|
43
|
-
disks: r
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* network info
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Table } from 'antd'
|
|
6
|
-
import { isEmpty } from 'lodash-es'
|
|
7
|
-
import { useEffect, useState } from 'react'
|
|
8
|
-
import { filesize } from 'filesize'
|
|
9
|
-
import copy from 'json-deep-copy'
|
|
10
|
-
import { ApiOutlined } from '@ant-design/icons'
|
|
11
|
-
|
|
12
|
-
export default function TerminalInfoDisk (props) {
|
|
13
|
-
const { network, isRemote, terminalInfos } = props
|
|
14
|
-
if (isEmpty(network) || !isRemote || !terminalInfos.includes('network')) {
|
|
15
|
-
return null
|
|
16
|
-
}
|
|
17
|
-
const [state, setter] = useState({
|
|
18
|
-
network: props.network,
|
|
19
|
-
time: Date.now()
|
|
20
|
-
})
|
|
21
|
-
function setState (ext) {
|
|
22
|
-
setter(s => {
|
|
23
|
-
return Object.assign({}, s, ext)
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
function updateTraffic () {
|
|
27
|
-
const network = copy(props.network)
|
|
28
|
-
const keys = Object.keys(network)
|
|
29
|
-
const net = copy(state.network)
|
|
30
|
-
const now = Date.now()
|
|
31
|
-
const { time } = state
|
|
32
|
-
const diff = (now - time) / 1000
|
|
33
|
-
for (const k of keys) {
|
|
34
|
-
const p = network[k]
|
|
35
|
-
const pv = net[k]
|
|
36
|
-
if (
|
|
37
|
-
diff > 0 &&
|
|
38
|
-
p &&
|
|
39
|
-
pv &&
|
|
40
|
-
p.download &&
|
|
41
|
-
pv.download &&
|
|
42
|
-
p.download > pv.download
|
|
43
|
-
) {
|
|
44
|
-
p.down = Math.floor((p.download - pv.download) / diff)
|
|
45
|
-
}
|
|
46
|
-
if (
|
|
47
|
-
diff > 0 &&
|
|
48
|
-
p &&
|
|
49
|
-
pv &&
|
|
50
|
-
p.upload &&
|
|
51
|
-
pv.upload &&
|
|
52
|
-
p.upload > pv.upload
|
|
53
|
-
) {
|
|
54
|
-
p.up = Math.floor((p.upload - pv.upload) / diff)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
setState({
|
|
58
|
-
network,
|
|
59
|
-
time: now
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
updateTraffic()
|
|
64
|
-
}, [props.network])
|
|
65
|
-
if (isEmpty(state)) {
|
|
66
|
-
return null
|
|
67
|
-
}
|
|
68
|
-
const arr = Object.keys(state.network).map(k => {
|
|
69
|
-
return {
|
|
70
|
-
name: k,
|
|
71
|
-
...state.network[k]
|
|
72
|
-
}
|
|
73
|
-
}).sort((a, b) => {
|
|
74
|
-
const ai = a.name.startsWith('eth') ? 100 : 10
|
|
75
|
-
const bi = b.name.startsWith('eth') ? 100 : 10
|
|
76
|
-
if (ai !== bi) {
|
|
77
|
-
return bi - ai
|
|
78
|
-
}
|
|
79
|
-
return a.name > b.name ? 1 : -1
|
|
80
|
-
})
|
|
81
|
-
const map = {
|
|
82
|
-
up: '↑',
|
|
83
|
-
down: '↓',
|
|
84
|
-
name: 'name',
|
|
85
|
-
ip: 'ipv4'
|
|
86
|
-
}
|
|
87
|
-
const col = ['name', 'ip', 'up', 'down'].map((k, i) => {
|
|
88
|
-
return {
|
|
89
|
-
title: map[k],
|
|
90
|
-
dataIndex: k,
|
|
91
|
-
key: k,
|
|
92
|
-
sorter: (a, b) => {
|
|
93
|
-
return a[k] > b[k] ? 1 : -1
|
|
94
|
-
},
|
|
95
|
-
render: (v) => {
|
|
96
|
-
if (k === 'up' || k === 'down') {
|
|
97
|
-
return filesize(Number.isFinite(v) ? v : 0)
|
|
98
|
-
}
|
|
99
|
-
return v
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
const ps = {
|
|
104
|
-
rowKey: 'name',
|
|
105
|
-
dataSource: arr,
|
|
106
|
-
bordered: true,
|
|
107
|
-
size: 'small',
|
|
108
|
-
columns: col,
|
|
109
|
-
pagination: false,
|
|
110
|
-
scroll: {
|
|
111
|
-
y: 180,
|
|
112
|
-
x: 'max-content'
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return (
|
|
116
|
-
<div className='terminal-info-section terminal-info-network'>
|
|
117
|
-
<div className='pd1y bold'><ApiOutlined /> Network</div>
|
|
118
|
-
<Table {...ps} />
|
|
119
|
-
</div>
|
|
120
|
-
)
|
|
121
|
-
}
|