@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
@@ -0,0 +1,173 @@
1
+ export const defaultAIPresets = [
2
+ {
3
+ id: 'atlascloud',
4
+ nameAI: 'AtlasCloud',
5
+ baseURLAI: 'https://api.atlascloud.ai/v1',
6
+ apiPathAI: '/chat/completions',
7
+ modelAI: 'deepseek-ai/deepseek-v4-flash',
8
+ authHeaderNameAI: 'Authorization: Bearer',
9
+ siteUrl: 'https://www.atlascloud.ai?ref=PCAEL2',
10
+ modelAIs: [
11
+ { value: 'deepseek-ai/deepseek-v4-flash' },
12
+ { value: 'zai-org/GLM-5.3' },
13
+ { value: 'moonshotai/Kimi-K3' }
14
+ ]
15
+ },
16
+ {
17
+ id: 'siliconflow',
18
+ nameAI: 'SiliconFlow',
19
+ baseURLAI: 'https://api.siliconflow.cn/v1',
20
+ apiPathAI: '/chat/completions',
21
+ modelAI: 'deepseek-ai/DeepSeek-V4-Flash',
22
+ authHeaderNameAI: 'Authorization: Bearer',
23
+ siteUrl: 'https://cloud.siliconflow.cn/i/Xa85Yz6R'
24
+ },
25
+ {
26
+ id: 'ApiSmart',
27
+ nameAI: 'ApiSmart',
28
+ baseURLAI: 'https://api.apismart.ai/v1',
29
+ apiPathAI: '/chat/completions',
30
+ modelAI: 'DEEPSEEK_V4_FLASH',
31
+ authHeaderNameAI: 'Authorization: Bearer',
32
+ siteUrl: 'https://apismart.ai/?utm_source=electerm_app&utm_medium=link&utm_campaign=electerm'
33
+ },
34
+ {
35
+ id: 'apimart',
36
+ nameAI: 'ApiMart',
37
+ baseURLAI: 'https://api.apimart.ai/v1',
38
+ apiPathAI: '/chat/completions',
39
+ modelAI: 'deepseek-v4-flash',
40
+ authHeaderNameAI: 'Authorization: Bearer',
41
+ siteUrl: 'https://go.apimart.ai/gh-electerm'
42
+ },
43
+ {
44
+ id: 'deepseek',
45
+ nameAI: 'DeepSeek',
46
+ baseURLAI: 'https://api.deepseek.com/v1',
47
+ apiPathAI: '/chat/completions',
48
+ modelAI: 'deepseek-chat',
49
+ authHeaderNameAI: 'Authorization: Bearer',
50
+ siteUrl: 'https://platform.deepseek.com/api_keys?utm_source=electerm_app&utm_medium=link&utm_campaign=electerm'
51
+ },
52
+ {
53
+ id: 'openai',
54
+ nameAI: 'OpenAI',
55
+ baseURLAI: 'https://api.openai.com/v1',
56
+ apiPathAI: '/chat/completions',
57
+ modelAI: 'gpt-4o',
58
+ authHeaderNameAI: 'Authorization: Bearer',
59
+ siteUrl: 'https://platform.openai.com/api-keys?utm_source=electerm_app&utm_medium=link&utm_campaign=electerm'
60
+ },
61
+ {
62
+ id: 'openai-responses',
63
+ nameAI: 'OpenAI (Responses API)',
64
+ baseURLAI: 'https://api.openai.com/v1',
65
+ apiPathAI: '/responses',
66
+ modelAI: 'gpt-4o',
67
+ authHeaderNameAI: 'Authorization: Bearer',
68
+ modelAIs: [
69
+ { value: 'gpt-4o' },
70
+ { value: 'gpt-4o-mini' },
71
+ { value: 'gpt-4.1' },
72
+ { value: 'o4-mini' }
73
+ ],
74
+ siteUrl: 'https://platform.openai.com/api-keys?utm_source=electerm_app&utm_medium=link&utm_campaign=electerm'
75
+ },
76
+ {
77
+ id: 'anthropic',
78
+ nameAI: 'Anthropic Claude',
79
+ baseURLAI: 'https://api.anthropic.com/v1',
80
+ apiPathAI: '/messages',
81
+ modelAI: 'claude-sonnet-4-5',
82
+ authHeaderNameAI: 'x-api-key',
83
+ modelAIs: [
84
+ { value: 'claude-sonnet-4-5' },
85
+ { value: 'claude-opus-4-1' },
86
+ { value: 'claude-haiku-4-5' }
87
+ ],
88
+ siteUrl: 'https://console.anthropic.com/settings/keys?utm_source=electerm_app&utm_medium=link&utm_campaign=electerm'
89
+ },
90
+ {
91
+ id: 'openrouter',
92
+ nameAI: 'OpenRouter',
93
+ baseURLAI: 'https://openrouter.ai/api/v1',
94
+ apiPathAI: '/chat/completions',
95
+ modelAI: 'openai/gpt-4o-mini',
96
+ authHeaderNameAI: 'Authorization: Bearer',
97
+ siteUrl: 'https://openrouter.ai/keys?utm_source=electerm_app&utm_medium=link&utm_campaign=electerm'
98
+ },
99
+ {
100
+ id: 'orcarouter',
101
+ nameAI: 'OrcaRouter',
102
+ baseURLAI: 'https://api.orcarouter.ai/v1',
103
+ apiPathAI: '/chat/completions',
104
+ modelAI: 'orcarouter/free',
105
+ authHeaderNameAI: 'Authorization: Bearer',
106
+ siteUrl: 'https://www.orcarouter.ai/ref/ref_2c4884ca28b88a82593f'
107
+ },
108
+ {
109
+ id: 'google',
110
+ nameAI: 'Google Gemini',
111
+ baseURLAI: 'https://generativelanguage.googleapis.com/v1beta/openai',
112
+ apiPathAI: '/chat/completions',
113
+ modelAI: 'gemini-2.0-flash',
114
+ authHeaderNameAI: 'Authorization: Bearer',
115
+ siteUrl: 'https://aistudio.google.com/apikey'
116
+ },
117
+ {
118
+ id: 'groq',
119
+ nameAI: 'Groq',
120
+ baseURLAI: 'https://api.groq.com/openai/v1',
121
+ apiPathAI: '/chat/completions',
122
+ modelAI: 'llama-3.3-70b-versatile',
123
+ authHeaderNameAI: 'Authorization: Bearer'
124
+ },
125
+ {
126
+ id: 'together',
127
+ nameAI: 'Together AI',
128
+ baseURLAI: 'https://api.together.xyz/v1',
129
+ apiPathAI: '/chat/completions',
130
+ modelAI: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
131
+ authHeaderNameAI: 'Authorization: Bearer'
132
+ },
133
+ {
134
+ id: 'mistral',
135
+ nameAI: 'Mistral AI',
136
+ baseURLAI: 'https://api.mistral.ai/v1',
137
+ apiPathAI: '/chat/completions',
138
+ modelAI: 'mistral-large-latest',
139
+ authHeaderNameAI: 'Authorization: Bearer'
140
+ },
141
+ {
142
+ id: 'xai',
143
+ nameAI: 'xAI (Grok)',
144
+ baseURLAI: 'https://api.x.ai/v1',
145
+ apiPathAI: '/chat/completions',
146
+ modelAI: 'grok-3-mini',
147
+ authHeaderNameAI: 'Authorization: Bearer'
148
+ },
149
+ {
150
+ id: 'perplexity',
151
+ nameAI: 'Perplexity',
152
+ baseURLAI: 'https://api.perplexity.ai',
153
+ apiPathAI: '/chat/completions',
154
+ modelAI: 'sonar',
155
+ authHeaderNameAI: 'Authorization: Bearer'
156
+ },
157
+ {
158
+ id: 'moonshot',
159
+ nameAI: 'Moonshot (Kimi)',
160
+ baseURLAI: 'https://api.moonshot.cn/v1',
161
+ apiPathAI: '/chat/completions',
162
+ modelAI: 'moonshot-v1-8k',
163
+ authHeaderNameAI: 'Authorization: Bearer'
164
+ }
165
+ ]
166
+
167
+ export function getAIPresets () {
168
+ const presets = [...defaultAIPresets]
169
+ if (window.et?.defaultAIPreset) {
170
+ presets.unshift(window.et.defaultAIPreset)
171
+ }
172
+ return presets
173
+ }
@@ -79,6 +79,24 @@
79
79
  opacity 0.4
80
80
  cursor not-allowed
81
81
 
82
+ .ai-chat-attachments
83
+ padding 0 2px
84
+ .ant-tag
85
+ margin 0
86
+ max-width 100%
87
+ overflow hidden
88
+ text-overflow ellipsis
89
+ white-space nowrap
90
+
91
+ .ai-chat-item-attachments
92
+ .ant-tag
93
+ margin-right 4px
94
+ margin-bottom 4px
95
+
96
+ .ai-attachment-dragover
97
+ outline 2px dashed #1890ff
98
+ outline-offset -2px
99
+
82
100
  .ai-chat-sessions-wrap
83
101
  width 100%
84
102
  height 100%
@@ -230,3 +248,68 @@
230
248
  transform rotate(0deg)
231
249
  to
232
250
  transform rotate(360deg)
251
+
252
+ .ai-chat-empty
253
+ position relative
254
+ width 100%
255
+ min-height 150px
256
+ display flex
257
+ align-items center
258
+ justify-content center
259
+ overflow hidden
260
+ padding 10px 0
261
+
262
+ .ai-chat-empty-inner
263
+ text-align center
264
+ padding 0 12px
265
+ max-width 100%
266
+
267
+ .ai-chat-watermark
268
+ width 86%
269
+ max-width 400px
270
+ aspect-ratio 766 / 266
271
+ margin 0 auto 14px
272
+ background-color var(--text)
273
+ opacity 0.55
274
+ pointer-events none
275
+ user-select none
276
+ -webkit-mask-image var(--ai-watermark)
277
+ mask-image var(--ai-watermark)
278
+ -webkit-mask-repeat no-repeat
279
+ mask-repeat no-repeat
280
+ -webkit-mask-position center
281
+ mask-position center
282
+ -webkit-mask-size contain
283
+ mask-size contain
284
+
285
+ .ai-chat-empty-title
286
+ font-size 15px
287
+ font-weight 500
288
+
289
+ .ai-chat-empty-hint
290
+ font-size 12px
291
+ margin-top 4px
292
+ color var(--text-dark)
293
+
294
+ .ai-chat-empty-examples
295
+ margin-top 14px
296
+ display flex
297
+ flex-direction column
298
+ align-items center
299
+ gap 6px
300
+
301
+ .ai-chat-empty-example
302
+ max-width 100%
303
+ font-size 12px
304
+ padding 5px 10px
305
+ border 1px solid var(--main-darker)
306
+ border-radius 14px
307
+ color var(--text-dark)
308
+ cursor pointer
309
+ transition all 0.2s
310
+ white-space nowrap
311
+ overflow hidden
312
+ text-overflow ellipsis
313
+ &:hover
314
+ border-color var(--primary)
315
+ color var(--text)
@@ -7,6 +7,10 @@ import { generateMosaicBackground } from './shapes'
7
7
 
8
8
  const themeDomId = 'css-overwrite-terminal-backgrounds'
9
9
 
10
+ // natural (intrinsic) width of images/electerm-watermark.png, used to keep the
11
+ // default terminal background at its original size on wide terminals
12
+ const watermarkWidth = 766
13
+
10
14
  function createBackgroundStyle (imagePath) {
11
15
  if (!imagePath || imagePath === '') {
12
16
  return ''
@@ -104,8 +108,15 @@ async function createStyleForTab (tab, props) {
104
108
  async function createGlobalStyle (props) {
105
109
  const st = await createBackgroundStyle(props.terminalBackgroundImagePath)
106
110
  if (!st) {
111
+ // Default terminal bg = the electerm watermark (766 x 266 natural size).
112
+ // Without an explicit size the image is just centered at 1:1, so on narrow
113
+ // panes (mobile / thin split columns) it gets cropped on both sides.
114
+ // `min(100%, 766px) auto` keeps the aspect ratio and scales it down to fit
115
+ // the pane width, while leaving the desktop rendering (= natural size)
116
+ // untouched.
107
117
  return '#container .session-batch-active .xterm-screen::before {' +
108
118
  'background-image: url("./images/electerm-watermark.png");' +
119
+ `background-size: min(100%, ${watermarkWidth}px) auto;` +
109
120
  '}'
110
121
  }
111
122
 
@@ -25,6 +25,7 @@ const bookmarkSchema = {
25
25
  compress: 'array - compression algorithms (zlib@openssh.com, zlib, none)',
26
26
  runScripts: 'array - run scripts after connected ({delay,script})',
27
27
  quickCommands: 'array - quick commands ({name,command})',
28
+ triggers: 'array - declarative auto triggers ({id,name,enabled,match:{type:text|regex,value,caseSensitive},action:{type:send|notify,value},sendEnter,mode:repeat|once|cooldown,cooldownMs})',
28
29
  proxy: 'string - proxy address (socks5://...)',
29
30
  proxyCommand: 'string - ssh proxy command, supports %h (host) %p (port) %r (username) placeholders, e.g. `netbird ssh proxy %h %p` or `cloudflared access ssh --hostname %h`',
30
31
  x11: 'boolean - enable x11 forwarding, default is false',
@@ -66,6 +67,7 @@ const bookmarkSchema = {
66
67
  loginPrompt: 'string - login prompt regex',
67
68
  passwordPrompt: 'string - password prompt regex',
68
69
  runScripts: 'array - run scripts after connected ({delay,script})',
70
+ triggers: 'array - declarative auto triggers, same shape as ssh.triggers',
69
71
  startDirectoryRemote: 'string - remote starting directory',
70
72
  startDirectoryLocal: 'string - local starting directory',
71
73
  profile: 'string - profile id',
@@ -88,6 +90,7 @@ const bookmarkSchema = {
88
90
  closeSequence: 'string - key sequence sent to the serial port when the user clicks "exit gracefully" in the terminal controls, supports \\n \\t \\r \\\\ and \\xHH hex bytes, default "\\x01ky" (Ctrl+A, k, y - GNU screen kill-window confirm)',
89
91
  closeSequenceDelay: 'number - ms to wait after sending closeSequence before closing the port, default 500',
90
92
  runScripts: 'array - run scripts after connected ({delay,script})',
93
+ triggers: 'array - declarative auto triggers, same shape as ssh.triggers',
91
94
  description: 'string - bookmark description'
92
95
  },
93
96
  vnc: {
@@ -144,6 +147,7 @@ const bookmarkSchema = {
144
147
  description: 'string - bookmark description',
145
148
  startDirectoryLocal: 'string - local starting directory',
146
149
  runScripts: 'array - run scripts after connected ({delay,script})',
150
+ triggers: 'array - declarative auto triggers, same shape as ssh.triggers',
147
151
  execWindows: 'string - Windows exec path (overrides global setting)',
148
152
  execMac: 'string - Mac exec path (overrides global setting)',
149
153
  execLinux: 'string - Linux exec path (overrides global setting)',
@@ -16,6 +16,7 @@ import ConnectionHopping from './connection-hopping.jsx'
16
16
  import TerminalBackgroundField from './terminal-background.jsx'
17
17
  import ExecSettingsField from './exec-settings-field.jsx'
18
18
  import useQuickCmds from './quick-commands.jsx'
19
+ import TriggersField from '../../triggers/triggers-field.jsx'
19
20
  import ProfileItem from './profile-item.jsx'
20
21
  import renderRunScripts from './run-scripts.jsx'
21
22
  import SerialPathSelector from './serial-path-selector.jsx'
@@ -173,6 +174,8 @@ export function renderFormItem (item, formItemLayout, form, ctxProps, index) {
173
174
  return <ProfileItem key={name} store={ctxProps.store} form={form} profileFilter={item.profileFilter} />
174
175
  case 'quickCommands':
175
176
  return <Fragment key={name}>{useQuickCmds(form, ctxProps.formData || {})}</Fragment>
177
+ case 'triggers':
178
+ return <TriggersField key={name} />
176
179
  case 'runScripts':
177
180
  return <Fragment key={name}>{renderRunScripts()}</Fragment>
178
181
  case 'serialPathSelector':
@@ -251,6 +251,12 @@ export const commonFields = {
251
251
  label: ''
252
252
  },
253
253
 
254
+ triggers: {
255
+ type: 'triggers',
256
+ name: 'triggers',
257
+ label: ''
258
+ },
259
+
254
260
  enableTerminalImage: {
255
261
  type: 'switch',
256
262
  name: 'enableTerminalImage',
@@ -360,6 +366,12 @@ export const quickCommandsTab = () => ({
360
366
  fields: [commonFields.quickCommands]
361
367
  })
362
368
 
369
+ export const triggersTab = () => ({
370
+ key: 'triggers',
371
+ label: e('triggers'),
372
+ fields: [commonFields.triggers]
373
+ })
374
+
363
375
  export const sshTunnelTab = () => ({
364
376
  key: 'tunnel',
365
377
  label: e('sshTunnel'),
@@ -7,7 +7,7 @@ import {
7
7
  getTerminalBackgroundDefaults
8
8
  } from '../common/init-values.js'
9
9
  import defaultSettings from '../../../common/default-setting.js'
10
- import { commonFields } from './common-fields.js'
10
+ import { commonFields, triggersTab } from './common-fields.js'
11
11
 
12
12
  const e = window.translate
13
13
 
@@ -56,7 +56,8 @@ const localConfig = {
56
56
  fields: [
57
57
  commonFields.quickCommands
58
58
  ]
59
- }
59
+ },
60
+ triggersTab()
60
61
  ]
61
62
  }
62
63
 
@@ -2,7 +2,7 @@ import { formItemLayout } from '../../../common/form-layout.js'
2
2
  import { terminalSerialType, commonBaudRates, commonDataBits, commonStopBits, commonParities, commonTxLineEndings, commonRxLineEndings } from '../../../common/constants.js'
3
3
  import defaultSettings from '../../../common/default-setting.js'
4
4
  import { createBaseInitValues, getTerminalBackgroundDefaults } from '../common/init-values.js'
5
- import { commonFields } from './common-fields.js'
5
+ import { commonFields, triggersTab } from './common-fields.js'
6
6
 
7
7
  const e = window.translate
8
8
 
@@ -91,7 +91,8 @@ const serialConfig = {
91
91
  fields: [
92
92
  { type: 'quickCommands', name: '__quick__', label: '' }
93
93
  ]
94
- }
94
+ },
95
+ triggersTab()
95
96
  ]
96
97
  }
97
98
 
@@ -3,7 +3,7 @@ import { formItemLayout } from '../../../common/form-layout.js'
3
3
  import { connectionMap, authTypeMap, defaultEnvLang } from '../../../common/constants.js'
4
4
  import defaultSetting from '../../../common/default-setting.js'
5
5
  import { createBaseInitValues, getTerminalDefaults, getSshDefaults, getTerminalBackgroundDefaults, getAuthTypeDefault } from '../common/init-values.js'
6
- import { sshAuthFields, sshSettings, quickCommandsTab, sshTunnelTab, connectionHoppingTab } from './common-fields.js'
6
+ import { sshAuthFields, sshSettings, quickCommandsTab, triggersTab, sshTunnelTab, connectionHoppingTab } from './common-fields.js'
7
7
 
8
8
  const e = window.translate
9
9
 
@@ -44,6 +44,7 @@ const sshConfig = {
44
44
  fields: sshSettings
45
45
  },
46
46
  quickCommandsTab(),
47
+ triggersTab(),
47
48
  sshTunnelTab(),
48
49
  connectionHoppingTab()
49
50
  ]
@@ -7,7 +7,7 @@ import {
7
7
  getTerminalBackgroundDefaults,
8
8
  getAuthTypeDefault
9
9
  } from '../common/init-values.js'
10
- import { telnetAuthFields, terminalSettings, quickCommandsTab } from './common-fields.js'
10
+ import { telnetAuthFields, terminalSettings, quickCommandsTab, triggersTab } from './common-fields.js'
11
11
 
12
12
  const e = window.translate
13
13
 
@@ -39,7 +39,8 @@ const telnetConfig = {
39
39
  label: e('settings'),
40
40
  fields: terminalSettings
41
41
  },
42
- quickCommandsTab()
42
+ quickCommandsTab(),
43
+ triggersTab()
43
44
  ]
44
45
  }
45
46
 
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Reusable filter control: a filter icon button that opens a checkbox list.
3
+ * Shared by the terminal info panel and the remote monitor bar.
4
+ */
5
+ import { Popover } from 'antd'
6
+ import { CheckOutlined, FilterOutlined } from '@ant-design/icons'
7
+ import './item-filter.styl'
8
+
9
+ const e = window.translate
10
+
11
+ export default function ItemFilter (props) {
12
+ const {
13
+ ids = [],
14
+ selected,
15
+ onToggle,
16
+ onOpenChange,
17
+ placement = 'bottomRight',
18
+ className = ''
19
+ } = props
20
+ const chosen = selected instanceof Set
21
+ ? selected
22
+ : new Set(selected || [])
23
+ const title = e('filter')
24
+ const total = ids.length
25
+ const count = ids.filter(id => chosen.has(id)).length
26
+ const content = (
27
+ <div className='item-filter-list' role='menu'>
28
+ {
29
+ ids.map(id => {
30
+ const active = chosen.has(id)
31
+ return (
32
+ <div
33
+ aria-checked={active}
34
+ className={'item-filter-item' + (active ? ' item-filter-item-on' : '')}
35
+ data-filter-item={id}
36
+ key={id}
37
+ onClick={() => onToggle(id)}
38
+ onKeyDown={event => {
39
+ if (event.key === 'Enter' || event.key === ' ') {
40
+ event.preventDefault()
41
+ onToggle(id)
42
+ }
43
+ }}
44
+ role='menuitemcheckbox'
45
+ tabIndex={0}
46
+ >
47
+ <span className='item-filter-check'>
48
+ {active ? <CheckOutlined /> : null}
49
+ </span>
50
+ <span className='item-filter-label'>{e(id)}</span>
51
+ </div>
52
+ )
53
+ })
54
+ }
55
+ </div>
56
+ )
57
+ return (
58
+ <Popover
59
+ content={content}
60
+ onOpenChange={onOpenChange}
61
+ placement={placement}
62
+ title={title}
63
+ trigger='click'
64
+ >
65
+ <button
66
+ aria-label={`${title} (${count}/${total})`}
67
+ className={'item-filter' + (className ? ` ${className}` : '')}
68
+ title={title}
69
+ type='button'
70
+ >
71
+ <FilterOutlined />
72
+ <span className='item-filter-count'>({count}/{total})</span>
73
+ </button>
74
+ </Popover>
75
+ )
76
+ }
@@ -0,0 +1,54 @@
1
+ .item-filter
2
+ display inline-flex
3
+ align-items center
4
+ justify-content center
5
+ gap 3px
6
+ height 24px
7
+ padding 0 4px
8
+ border 0
9
+ background transparent
10
+ color var(--text-dark)
11
+ font-size 14px
12
+ cursor pointer
13
+ &:hover,
14
+ &:focus-visible
15
+ color var(--text)
16
+ &:focus-visible
17
+ outline 1px solid var(--primary)
18
+ outline-offset 1px
19
+
20
+ .item-filter-count
21
+ color var(--text-disabled)
22
+ font-size 11px
23
+ font-variant-numeric tabular-nums
24
+
25
+ .item-filter-list
26
+ min-width 140px
27
+ padding 2px 0
28
+
29
+ .item-filter-item
30
+ display flex
31
+ align-items center
32
+ gap 6px
33
+ padding 4px 8px
34
+ color var(--text)
35
+ cursor pointer
36
+ &:hover
37
+ background var(--main-light)
38
+ &:focus-visible
39
+ outline 1px solid var(--primary)
40
+ outline-offset -1px
41
+
42
+ .item-filter-item-on
43
+ color var(--primary)
44
+
45
+ .item-filter-check
46
+ flex-shrink 0
47
+ width 12px
48
+ font-size 10px
49
+
50
+ .item-filter-label
51
+ min-width 0
52
+ overflow hidden
53
+ text-overflow ellipsis
54
+ white-space nowrap
@@ -0,0 +1,34 @@
1
+ import { Component } from 'react'
2
+
3
+ /**
4
+ * Error boundary for locally lazy-loaded components.
5
+ *
6
+ * A failed chunk fetch (stale chunk after a web-app redeploy, flaky network)
7
+ * throws during render. Without a boundary like this one the error bubbles to
8
+ * the root ErrorBoundary in `main/index.jsx`, which swaps the *whole* app —
9
+ * every open terminal/ssh tab included — for the error page. Far too much
10
+ * collateral for one small chunk that failed to load.
11
+ *
12
+ * Renders nothing when the child failed to load, reports the error through the
13
+ * app's notification, and lets the caller react (e.g. release a mount latch so
14
+ * the next request retries).
15
+ *
16
+ * Note: `React.lazy` caches a rejected `import()` forever, so retrying needs a
17
+ * *new* lazy component — see `text-editor-entry.jsx` for the `useMemo` version.
18
+ */
19
+ export default class LazyBoundary extends Component {
20
+ state = { failed: false }
21
+
22
+ static getDerivedStateFromError () {
23
+ return { failed: true }
24
+ }
25
+
26
+ componentDidCatch (error) {
27
+ window.store.onError(error)
28
+ this.props.onError?.(error)
29
+ }
30
+
31
+ render () {
32
+ return this.state.failed ? null : this.props.children
33
+ }
34
+ }
@@ -11,7 +11,9 @@ const opacityDomId = 'opacity-style'
11
11
  * @returns {null}
12
12
  */
13
13
  export default function Opacity ({ opacity }) {
14
- // Default to 1 if opacity is not provided
14
+ // Default to 1 if opacity is not provided.
15
+ // window opacity is electron-only: this component is lazy-loaded and never
16
+ // rendered (nor loaded) in web app — see main.jsx
15
17
  const currentOpacity = opacity !== undefined ? opacity : 1
16
18
  const prevRef = useRef(null)
17
19