@electerm/electerm-react 5.3.26 → 5.5.6
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 +14 -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/footer-entry.jsx +35 -5
- package/client/components/footer/footer.styl +8 -1
- 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/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 +7 -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/side-panel-r/side-panel-r.jsx +2 -2
- 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 +88 -0
- package/client/components/terminal/mixins/term-context-menu.jsx +291 -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 +136 -0
- package/client/components/terminal/shell-detect.js +37 -0
- package/client/components/terminal/shell.js +0 -28
- package/client/components/terminal/shortcut-bar-entry.jsx +6 -3
- package/client/components/terminal/shortcut-bar.jsx +4 -6
- package/client/components/terminal/shortcut-bar.styl +4 -0
- package/client/components/terminal/ssh-reload-state.js +56 -0
- package/client/components/terminal/startup-queue.js +379 -0
- package/client/components/terminal/terminal-interactive.jsx +32 -0
- package/client/components/terminal/terminal.jsx +102 -2007
- 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 +9 -1
- 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 +41 -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/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 { shouldCaptureSshReloadState } from '../components/terminal/ssh-reload-state.js'
|
|
21
|
+
|
|
22
|
+
function captureSshSessionState (tab, config) {
|
|
23
|
+
return shouldCaptureSshReloadState(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,10 @@ export default Store => {
|
|
|
96
103
|
const oldTab = tabs[index]
|
|
97
104
|
const oldBatch = oldTab.batch
|
|
98
105
|
|
|
106
|
+
// Reload state is captured only for an SSH refresh/reconnect. Closing a
|
|
107
|
+
// tab goes through removeTabs and never reaches this code path.
|
|
108
|
+
const reloadState = captureSshSessionState(oldTab, store.config)
|
|
109
|
+
|
|
99
110
|
// Create copy of old tab with new ID
|
|
100
111
|
const newTab = {
|
|
101
112
|
...oldTab,
|
|
@@ -103,6 +114,10 @@ export default Store => {
|
|
|
103
114
|
id: generate(), // Need to create new ID
|
|
104
115
|
status: statusMap.processing // Reset status
|
|
105
116
|
}
|
|
117
|
+
delete newTab._reloadState
|
|
118
|
+
if (reloadState) {
|
|
119
|
+
newTab._reloadState = reloadState
|
|
120
|
+
}
|
|
106
121
|
|
|
107
122
|
// Add new tab at next index
|
|
108
123
|
tabs.splice(index + 1, 0, newTab)
|
|
@@ -153,6 +168,7 @@ export default Store => {
|
|
|
153
168
|
}
|
|
154
169
|
|
|
155
170
|
const sourceTab = tabs[targetIndex]
|
|
171
|
+
const sessionState = captureSshSessionState(sourceTab, store.config)
|
|
156
172
|
const duplicatedTab = {
|
|
157
173
|
...deepCopy(sourceTab),
|
|
158
174
|
id: generate(),
|
|
@@ -160,6 +176,10 @@ export default Store => {
|
|
|
160
176
|
status: statusMap.processing,
|
|
161
177
|
isTransporting: undefined
|
|
162
178
|
}
|
|
179
|
+
delete duplicatedTab._reloadState
|
|
180
|
+
if (sessionState) {
|
|
181
|
+
duplicatedTab._reloadState = sessionState
|
|
182
|
+
}
|
|
163
183
|
|
|
164
184
|
// Insert the duplicated tab after the source tab
|
|
165
185
|
tabs.splice(targetIndex + 1, 0, duplicatedTab)
|
|
@@ -361,7 +381,19 @@ export default Store => {
|
|
|
361
381
|
const { store } = window
|
|
362
382
|
const { tabs } = store
|
|
363
383
|
newTab.tabCount = store.nextTabCount()
|
|
364
|
-
|
|
384
|
+
// Sanitize batch: MCP/AI callers may pass a missing, string, or
|
|
385
|
+
// out-of-range batch which would crash Sessions (sizes[batch]
|
|
386
|
+
// undefined). Clamp to a valid pane index for the current layout.
|
|
387
|
+
let batchNum = batch ?? newTab.batch ?? window.openTabBatch ?? window.store.currentLayoutBatch
|
|
388
|
+
batchNum = Number(batchNum)
|
|
389
|
+
const maxBatch = (splitConfig[store.layout] && splitConfig[store.layout].children) || 1
|
|
390
|
+
if (!Number.isInteger(batchNum) || batchNum < 0 || batchNum >= maxBatch) {
|
|
391
|
+
batchNum = Number(window.store.currentLayoutBatch) || 0
|
|
392
|
+
if (!Number.isInteger(batchNum) || batchNum < 0 || batchNum >= maxBatch) {
|
|
393
|
+
batchNum = 0
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
newTab.batch = batchNum
|
|
365
397
|
if (!newTab.id) {
|
|
366
398
|
newTab.id = generate()
|
|
367
399
|
}
|
|
@@ -376,7 +408,6 @@ export default Store => {
|
|
|
376
408
|
} else {
|
|
377
409
|
tabs.push(newTab)
|
|
378
410
|
}
|
|
379
|
-
const batchNum = newTab.batch
|
|
380
411
|
store[`activeTabId${batchNum}`] = newTab.id
|
|
381
412
|
store.activeTabId = newTab.id
|
|
382
413
|
store.currentLayoutBatch = batchNum
|
|
@@ -393,7 +424,8 @@ export default Store => {
|
|
|
393
424
|
'execLinuxArgs',
|
|
394
425
|
'setEnv',
|
|
395
426
|
'runScripts',
|
|
396
|
-
'interactiveValues'
|
|
427
|
+
'interactiveValues',
|
|
428
|
+
'triggers'
|
|
397
429
|
]
|
|
398
430
|
|
|
399
431
|
Store.prototype.ipcOpenTab = function (parsed) {
|
|
@@ -454,12 +486,17 @@ export default Store => {
|
|
|
454
486
|
const defaultStatus = statusMap.processing
|
|
455
487
|
const { layout, currentLayoutBatch } = store
|
|
456
488
|
const ntb = deepCopy(tab)
|
|
489
|
+
const sessionState = captureSshSessionState(tab, store.config)
|
|
457
490
|
Object.assign(ntb, {
|
|
458
491
|
id: generate(),
|
|
459
492
|
status: defaultStatus,
|
|
460
493
|
isTransporting: undefined,
|
|
461
494
|
pane: paneMap.terminal
|
|
462
495
|
})
|
|
496
|
+
delete ntb._reloadState
|
|
497
|
+
if (sessionState) {
|
|
498
|
+
ntb._reloadState = sessionState
|
|
499
|
+
}
|
|
463
500
|
let maxBatch = splitConfig[layout].children
|
|
464
501
|
if (maxBatch < 2) {
|
|
465
502
|
maxBatch = 2
|
|
@@ -548,6 +585,7 @@ export default Store => {
|
|
|
548
585
|
'sshTunnelResults',
|
|
549
586
|
'displayRaw',
|
|
550
587
|
'autoReConnect',
|
|
588
|
+
'_reloadState',
|
|
551
589
|
'isPinned'
|
|
552
590
|
]
|
|
553
591
|
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
|
-
}
|