@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.
Files changed (146) hide show
  1. package/README.md +246 -12
  2. package/client/common/choose-save-folder.js +21 -2
  3. package/client/common/constants.js +20 -6
  4. package/client/common/create-title.jsx +6 -0
  5. package/client/common/default-setting.js +15 -0
  6. package/client/common/init-setting-item.js +5 -0
  7. package/client/common/platform.js +24 -0
  8. package/client/components/ai/agent-tool-call-card.jsx +10 -2
  9. package/client/components/ai/agent.js +4 -1
  10. package/client/components/ai/ai-attachments.js +87 -0
  11. package/client/components/ai/ai-chat-empty.jsx +43 -0
  12. package/client/components/ai/ai-chat-entry.jsx +13 -4
  13. package/client/components/ai/ai-chat-history-item.jsx +28 -4
  14. package/client/components/ai/ai-chat-history.jsx +2 -1
  15. package/client/components/ai/ai-chat.jsx +104 -13
  16. package/client/components/ai/ai-config-props.js +4 -130
  17. package/client/components/ai/ai-config.jsx +135 -15
  18. package/client/components/ai/ai-presets.js +173 -0
  19. package/client/components/ai/ai.styl +83 -0
  20. package/client/components/bg/css-overwrite.jsx +11 -0
  21. package/client/components/bookmark-form/bookmark-schema.js +4 -0
  22. package/client/components/bookmark-form/common/fields.jsx +3 -0
  23. package/client/components/bookmark-form/config/common-fields.js +12 -0
  24. package/client/components/bookmark-form/config/local.js +3 -2
  25. package/client/components/bookmark-form/config/serial.js +3 -2
  26. package/client/components/bookmark-form/config/ssh.js +2 -1
  27. package/client/components/bookmark-form/config/telnet.js +3 -2
  28. package/client/components/common/item-filter.jsx +76 -0
  29. package/client/components/common/item-filter.styl +54 -0
  30. package/client/components/common/lazy-boundary.jsx +34 -0
  31. package/client/components/common/opacity.jsx +3 -1
  32. package/client/components/footer/cmd-history.jsx +132 -45
  33. package/client/components/footer/cmd-history.styl +42 -4
  34. package/client/components/footer/footer-entry.jsx +35 -5
  35. package/client/components/footer/footer.styl +8 -1
  36. package/client/components/footer/multi-tab-run-modal.jsx +83 -0
  37. package/client/components/footer/tab-select.jsx +34 -17
  38. package/client/components/layout/layout.jsx +23 -31
  39. package/client/components/layout/pixed.js +3 -0
  40. package/client/components/main/main.jsx +55 -28
  41. package/client/components/main/term-fullscreen.styl +9 -3
  42. package/client/components/quick-commands/qm.styl +3 -2
  43. package/client/components/quick-commands/quick-command-create-modal.jsx +60 -0
  44. package/client/components/quick-commands/quick-commands-form-elem.jsx +10 -4
  45. package/client/components/rdp/file-transfer.js +4 -4
  46. package/client/components/remote-monitor/monitor-details.jsx +458 -0
  47. package/client/components/remote-monitor/monitor-details.styl +87 -0
  48. package/client/components/remote-monitor/monitor-model.js +764 -0
  49. package/client/components/remote-monitor/remote-monitor-bar-entry.jsx +18 -0
  50. package/client/components/remote-monitor/remote-monitor-bar.jsx +344 -0
  51. package/client/components/remote-monitor/remote-monitor-bar.styl +106 -0
  52. package/client/components/remote-monitor/session-monitor.js +361 -0
  53. package/client/components/remote-monitor/use-monitor-details.js +10 -0
  54. package/client/components/remote-monitor/visibility.js +18 -0
  55. package/client/components/session/session-control.jsx +29 -2
  56. package/client/components/session/session.jsx +40 -18
  57. package/client/components/session/sessions.jsx +5 -2
  58. package/client/components/setting-panel/setting-common.jsx +25 -56
  59. package/client/components/setting-panel/setting-modal.jsx +57 -42
  60. package/client/components/setting-panel/setting-terminal.jsx +8 -2
  61. package/client/components/setting-panel/setting.styl +1 -1
  62. package/client/components/setting-panel/tab-triggers.jsx +71 -0
  63. package/client/components/sftp/file-compare-modal.jsx +6 -3
  64. package/client/components/sftp/file-item.jsx +5 -3
  65. package/client/components/sftp/owner-list.js +11 -2
  66. package/client/components/sftp/sftp-entry.jsx +4 -0
  67. package/client/components/side-panel-r/side-panel-r.jsx +2 -2
  68. package/client/components/sidebar/side-panel.jsx +1 -1
  69. package/client/components/sys-menu/menu-btn.jsx +30 -1
  70. package/client/components/tabs/index.jsx +2 -2
  71. package/client/components/tabs/tab-title.jsx +3 -2
  72. package/client/components/tabs/tab.jsx +5 -1
  73. package/client/components/terminal/attach-addon-custom.js +295 -49
  74. package/client/components/terminal/automation/index.js +64 -0
  75. package/client/components/terminal/automation/keys.js +49 -0
  76. package/client/components/terminal/automation/stream-matcher.js +82 -0
  77. package/client/components/terminal/automation/strip-ansi.js +19 -0
  78. package/client/components/terminal/automation/trigger-engine.js +233 -0
  79. package/client/components/terminal/automation/trigger-presets.js +110 -0
  80. package/client/components/terminal/command-tracker-addon.js +31 -1
  81. package/client/components/terminal/mixins/index.js +27 -0
  82. package/client/components/terminal/mixins/term-attach.js +123 -0
  83. package/client/components/terminal/mixins/term-config.jsx +84 -0
  84. package/client/components/terminal/mixins/term-content.js +126 -0
  85. package/client/components/terminal/mixins/term-context-menu.jsx +323 -0
  86. package/client/components/terminal/mixins/term-file-drop.js +149 -0
  87. package/client/components/terminal/mixins/term-init.js +122 -0
  88. package/client/components/terminal/mixins/term-log.jsx +96 -0
  89. package/client/components/terminal/mixins/term-resize.js +87 -0
  90. package/client/components/terminal/mixins/term-socket.js +433 -0
  91. package/client/components/terminal/mixins/term-suggestions.js +224 -0
  92. package/client/components/terminal/mixins/term-theme.js +190 -0
  93. package/client/components/terminal/mixins/term-touch.js +523 -0
  94. package/client/components/terminal/shell-detect.js +37 -0
  95. package/client/components/terminal/shell.js +0 -28
  96. package/client/components/terminal/shortcut-bar-defs.js +30 -4
  97. package/client/components/terminal/shortcut-bar-edit.jsx +40 -9
  98. package/client/components/terminal/shortcut-bar-entry.jsx +6 -3
  99. package/client/components/terminal/shortcut-bar.jsx +17 -10
  100. package/client/components/terminal/shortcut-bar.styl +4 -0
  101. package/client/components/terminal/ssh-reload-state.js +96 -0
  102. package/client/components/terminal/startup-queue.js +433 -0
  103. package/client/components/terminal/terminal-interactive.jsx +32 -0
  104. package/client/components/terminal/terminal-select-text.jsx +69 -0
  105. package/client/components/terminal/terminal.jsx +227 -2064
  106. package/client/components/terminal/terminal.styl +61 -0
  107. package/client/components/terminal/transfer-client-base.js +5 -29
  108. package/client/components/terminal/trzsz-client.js +1 -3
  109. package/client/components/terminal/xmodem-client.js +1 -3
  110. package/client/components/terminal/xterm-loader.js +7 -0
  111. package/client/components/terminal/zmodem-client.js +1 -3
  112. package/client/components/terminal-info/base.jsx +18 -70
  113. package/client/components/terminal-info/terminal-info-entry.jsx +13 -5
  114. package/client/components/terminal-info/terminal-info.jsx +50 -59
  115. package/client/components/terminal-info/terminal-info.styl +6 -49
  116. package/client/components/text-editor/open-text-editor.js +30 -0
  117. package/client/components/text-editor/text-editor-entry.jsx +17 -6
  118. package/client/components/text-editor/text-editor.jsx +14 -0
  119. package/client/components/triggers/trigger-editor.jsx +246 -0
  120. package/client/components/triggers/trigger-form.jsx +200 -0
  121. package/client/components/triggers/trigger-session-modal.jsx +199 -0
  122. package/client/components/triggers/trigger-setting-list.jsx +89 -0
  123. package/client/components/triggers/trigger-transport.jsx +60 -0
  124. package/client/components/triggers/triggers-field.jsx +19 -0
  125. package/client/components/widgets/widgets-list.jsx +11 -1
  126. package/client/css/mobile.styl +29 -8
  127. package/client/store/common.js +12 -1
  128. package/client/store/init-state.js +7 -0
  129. package/client/store/mcp-handler.js +158 -30
  130. package/client/store/setting.js +9 -0
  131. package/client/store/store.js +2 -0
  132. package/client/store/sync.js +2 -0
  133. package/client/store/tab.js +42 -3
  134. package/client/store/trigger.js +118 -0
  135. package/client/store/watch.js +13 -5
  136. package/client/views/index.pug +3 -0
  137. package/package.json +1 -1
  138. package/client/components/terminal-info/activity.jsx +0 -57
  139. package/client/components/terminal-info/data-cols-parser.jsx +0 -54
  140. package/client/components/terminal-info/disk.jsx +0 -45
  141. package/client/components/terminal-info/format-disks.js +0 -45
  142. package/client/components/terminal-info/network.jsx +0 -121
  143. package/client/components/terminal-info/resource.jsx +0 -99
  144. package/client/components/terminal-info/run-cmd.jsx +0 -263
  145. package/client/components/terminal-info/sys-info.jsx +0 -56
  146. package/client/components/terminal-info/up.jsx +0 -17
@@ -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)
@@ -845,6 +845,8 @@ export default (Store) => {
845
845
  'addTimeStampToTermLog',
846
846
  'showHiddenFilesOnSftpStart',
847
847
  'terminalInfos',
848
+ 'remoteMonitorBarEnabled',
849
+ 'remoteMonitorBarItems',
848
850
  'filePropsEnabled',
849
851
  'hideIP',
850
852
  'terminalTimeout',
@@ -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
- newTab.batch = batch ?? newTab.batch ?? window.openTabBatch ?? window.store.currentLayoutBatch
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
+ }
@@ -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
- await update(
55
- `${name}:order`,
56
- newOrder
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
 
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "5.3.26",
3
+ "version": "5.5.9",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
@@ -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
- }