@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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable declarative-trigger editor.
|
|
3
|
+
* Controlled component: props { value, onChange }.
|
|
4
|
+
* Used by bookmark form tab, footer session modal and anywhere else.
|
|
5
|
+
* Supports UI editing, JSON editing and built-in presets.
|
|
6
|
+
*/
|
|
7
|
+
import { useState } from 'react'
|
|
8
|
+
import {
|
|
9
|
+
Button,
|
|
10
|
+
Switch,
|
|
11
|
+
Modal,
|
|
12
|
+
Form,
|
|
13
|
+
Input,
|
|
14
|
+
Dropdown,
|
|
15
|
+
Popconfirm,
|
|
16
|
+
Empty,
|
|
17
|
+
Tag,
|
|
18
|
+
Tooltip,
|
|
19
|
+
message
|
|
20
|
+
} from 'antd'
|
|
21
|
+
import {
|
|
22
|
+
PlusOutlined,
|
|
23
|
+
CopyOutlined,
|
|
24
|
+
DeleteOutlined,
|
|
25
|
+
EditOutlined,
|
|
26
|
+
CodeOutlined,
|
|
27
|
+
ThunderboltOutlined
|
|
28
|
+
} from '@ant-design/icons'
|
|
29
|
+
import TriggerForm, { buildTriggerFromFormValues } from './trigger-form.jsx'
|
|
30
|
+
import {
|
|
31
|
+
triggerPresets,
|
|
32
|
+
buildTriggerFromPreset,
|
|
33
|
+
buildEmptyTrigger
|
|
34
|
+
} from '../terminal/automation/trigger-presets.js'
|
|
35
|
+
import { validateTriggers } from '../terminal/automation/trigger-engine.js'
|
|
36
|
+
import uid from '../../common/uid'
|
|
37
|
+
|
|
38
|
+
const e = window.translate
|
|
39
|
+
|
|
40
|
+
export function matchSummary (t) {
|
|
41
|
+
const type = t.match?.type === 'regex' ? 're' : 'text'
|
|
42
|
+
const v = t.match?.value || ''
|
|
43
|
+
const short = v.length > 42 ? v.slice(0, 42) + '…' : v
|
|
44
|
+
return `[${type}] ${short}`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function actionSummary (t) {
|
|
48
|
+
if (t.action?.type === 'notify') {
|
|
49
|
+
return 'notify'
|
|
50
|
+
}
|
|
51
|
+
const v = t.action?.value || ''
|
|
52
|
+
const short = v.length > 24 ? v.slice(0, 24) + '…' : JSON.stringify(v)
|
|
53
|
+
return `send ${short}${t.sendEnter === false ? '' : ' +⏎'}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function TriggerEditor ({ value, onChange }) {
|
|
57
|
+
const list = Array.isArray(value) ? value : []
|
|
58
|
+
const [editing, setEditing] = useState(null)
|
|
59
|
+
const [jsonOpen, setJsonOpen] = useState(false)
|
|
60
|
+
const [jsonText, setJsonText] = useState('')
|
|
61
|
+
const [form] = Form.useForm()
|
|
62
|
+
|
|
63
|
+
const emit = (next) => {
|
|
64
|
+
onChange?.(next)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleToggle = (id, enabled) => {
|
|
68
|
+
emit(list.map(t => t.id === id ? { ...t, enabled } : t))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleDel = (id) => {
|
|
72
|
+
emit(list.filter(t => t.id !== id))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const handleDuplicate = (item) => {
|
|
76
|
+
const copy = { ...JSON.parse(JSON.stringify(item)), id: uid(), name: (item.name || '') + ' (copy)' }
|
|
77
|
+
emit([...list, copy])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const openAdd = () => {
|
|
81
|
+
setEditing(buildEmptyTrigger(uid))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const openEdit = (item) => {
|
|
85
|
+
setEditing(JSON.parse(JSON.stringify(item)))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const handlePreset = ({ key }) => {
|
|
89
|
+
const preset = triggerPresets[Number(key)]
|
|
90
|
+
if (!preset) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
emit([...list, buildTriggerFromPreset(preset, uid)])
|
|
94
|
+
message.success('OK')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const handleSaveEdit = async () => {
|
|
98
|
+
try {
|
|
99
|
+
const v = await form.validateFields()
|
|
100
|
+
const next = buildTriggerFromFormValues(editing, v)
|
|
101
|
+
const errors = validateTriggers([next])
|
|
102
|
+
if (errors.length) {
|
|
103
|
+
message.error(errors[0])
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
const exists = list.some(t => t.id === next.id)
|
|
107
|
+
emit(exists ? list.map(t => t.id === next.id ? next : t) : [...list, next])
|
|
108
|
+
setEditing(null)
|
|
109
|
+
} catch (err) {
|
|
110
|
+
// validation failed, keep modal open
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const openJson = () => {
|
|
115
|
+
setJsonText(JSON.stringify(list, null, 2))
|
|
116
|
+
setJsonOpen(true)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const applyJson = () => {
|
|
120
|
+
let parsed
|
|
121
|
+
try {
|
|
122
|
+
parsed = JSON.parse(jsonText)
|
|
123
|
+
} catch (err) {
|
|
124
|
+
message.error('Invalid JSON: ' + err.message)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
if (!Array.isArray(parsed)) {
|
|
128
|
+
message.error('JSON must be an array')
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
const normalized = parsed.map(t => ({
|
|
132
|
+
...t,
|
|
133
|
+
id: t.id || uid()
|
|
134
|
+
}))
|
|
135
|
+
const errors = validateTriggers(normalized)
|
|
136
|
+
if (errors.length) {
|
|
137
|
+
message.error(errors[0])
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
emit(normalized)
|
|
141
|
+
setJsonOpen(false)
|
|
142
|
+
message.success('OK')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const presetMenu = {
|
|
146
|
+
items: triggerPresets.map((p, i) => ({
|
|
147
|
+
key: String(i),
|
|
148
|
+
label: p.name
|
|
149
|
+
})),
|
|
150
|
+
onClick: handlePreset
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div className='trigger-editor'>
|
|
155
|
+
<div className='pd1b' style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
156
|
+
<Button
|
|
157
|
+
size='small'
|
|
158
|
+
type='primary'
|
|
159
|
+
icon={<PlusOutlined />}
|
|
160
|
+
onClick={openAdd}
|
|
161
|
+
>
|
|
162
|
+
{e('new')}
|
|
163
|
+
</Button>
|
|
164
|
+
<Dropdown menu={presetMenu} trigger={['click']}>
|
|
165
|
+
<Button size='small' icon={<ThunderboltOutlined />}>
|
|
166
|
+
{e('presets')}
|
|
167
|
+
</Button>
|
|
168
|
+
</Dropdown>
|
|
169
|
+
<Button
|
|
170
|
+
size='small'
|
|
171
|
+
icon={<CodeOutlined />}
|
|
172
|
+
onClick={openJson}
|
|
173
|
+
>
|
|
174
|
+
JSON
|
|
175
|
+
</Button>
|
|
176
|
+
</div>
|
|
177
|
+
{
|
|
178
|
+
!list.length
|
|
179
|
+
? <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='No triggers yet — add one or pick a preset' />
|
|
180
|
+
: list.map(t => (
|
|
181
|
+
<div
|
|
182
|
+
key={t.id}
|
|
183
|
+
className='trigger-item pd1x pd1y mg1b'
|
|
184
|
+
style={{ border: '1px solid var(--border, #333)', borderRadius: 4 }}
|
|
185
|
+
>
|
|
186
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
187
|
+
<Switch
|
|
188
|
+
size='small'
|
|
189
|
+
checked={t.enabled !== false}
|
|
190
|
+
onChange={(v) => handleToggle(t.id, v)}
|
|
191
|
+
/>
|
|
192
|
+
<b className='elli' style={{ flex: 1 }} title={t.name}>
|
|
193
|
+
{t.name || 'Unnamed'}
|
|
194
|
+
</b>
|
|
195
|
+
<Tag>{t.mode || 'cooldown'}</Tag>
|
|
196
|
+
<Tooltip title={e('edit')}>
|
|
197
|
+
<EditOutlined className='pointer' onClick={() => openEdit(t)} />
|
|
198
|
+
</Tooltip>
|
|
199
|
+
<Tooltip title={e('duplicate')}>
|
|
200
|
+
<CopyOutlined className='pointer' onClick={() => handleDuplicate(t)} />
|
|
201
|
+
</Tooltip>
|
|
202
|
+
<Popconfirm
|
|
203
|
+
title={e('del')}
|
|
204
|
+
onConfirm={() => handleDel(t.id)}
|
|
205
|
+
>
|
|
206
|
+
<DeleteOutlined className='pointer' />
|
|
207
|
+
</Popconfirm>
|
|
208
|
+
</div>
|
|
209
|
+
<div className='mg1t small muted elli' title={matchSummary(t) + ' → ' + actionSummary(t)}>
|
|
210
|
+
{matchSummary(t)} → {actionSummary(t)}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
))
|
|
214
|
+
}
|
|
215
|
+
<Modal
|
|
216
|
+
open={!!editing}
|
|
217
|
+
title={(editing?.name ? e('edit') : e('new')) + ' ' + e('triggers')}
|
|
218
|
+
onCancel={() => setEditing(null)}
|
|
219
|
+
onOk={handleSaveEdit}
|
|
220
|
+
destroyOnHidden
|
|
221
|
+
width={640}
|
|
222
|
+
>
|
|
223
|
+
{editing ? <TriggerForm form={form} initial={editing} /> : null}
|
|
224
|
+
</Modal>
|
|
225
|
+
<Modal
|
|
226
|
+
open={jsonOpen}
|
|
227
|
+
title='JSON'
|
|
228
|
+
onCancel={() => setJsonOpen(false)}
|
|
229
|
+
onOk={applyJson}
|
|
230
|
+
destroyOnHidden
|
|
231
|
+
width={640}
|
|
232
|
+
>
|
|
233
|
+
<Input.TextArea
|
|
234
|
+
rows={14}
|
|
235
|
+
value={jsonText}
|
|
236
|
+
onChange={(ev) => setJsonText(ev.target.value)}
|
|
237
|
+
spellCheck={false}
|
|
238
|
+
style={{ fontFamily: 'monospace' }}
|
|
239
|
+
/>
|
|
240
|
+
<div className='mg1t small muted'>
|
|
241
|
+
JSON array of trigger rules. Invalid regex or missing match will be rejected.
|
|
242
|
+
</div>
|
|
243
|
+
</Modal>
|
|
244
|
+
</div>
|
|
245
|
+
)
|
|
246
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single trigger rule form (used inside a Modal by TriggerEditor).
|
|
3
|
+
* Controlled via antd Form, initialValues = editing rule.
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect } from 'react'
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
Dropdown,
|
|
9
|
+
Form,
|
|
10
|
+
Input,
|
|
11
|
+
Switch,
|
|
12
|
+
Select,
|
|
13
|
+
InputNumber,
|
|
14
|
+
Radio
|
|
15
|
+
} from 'antd'
|
|
16
|
+
import { CaretDownOutlined } from '@ant-design/icons'
|
|
17
|
+
import {
|
|
18
|
+
triggerActionTypes,
|
|
19
|
+
triggerMatchTypes,
|
|
20
|
+
triggerModes,
|
|
21
|
+
triggerPresets
|
|
22
|
+
} from '../terminal/automation/trigger-presets.js'
|
|
23
|
+
import uid from '../../common/uid'
|
|
24
|
+
|
|
25
|
+
const e = window.translate
|
|
26
|
+
|
|
27
|
+
// Build a full trigger rule from form values. `editing` is the rule being
|
|
28
|
+
// edited (or a {id:''} placeholder for a new one).
|
|
29
|
+
export function buildTriggerFromFormValues (editing = {}, v) {
|
|
30
|
+
return {
|
|
31
|
+
...(editing.id ? editing : {}),
|
|
32
|
+
id: editing.id || uid(),
|
|
33
|
+
name: v.name,
|
|
34
|
+
enabled: v.enabled !== false,
|
|
35
|
+
match: {
|
|
36
|
+
type: v.matchType,
|
|
37
|
+
value: v.matchValue,
|
|
38
|
+
caseSensitive: !!v.caseSensitive
|
|
39
|
+
},
|
|
40
|
+
action: {
|
|
41
|
+
type: v.actionType,
|
|
42
|
+
value: v.actionType === 'notify' ? '' : (v.actionValue || '')
|
|
43
|
+
},
|
|
44
|
+
sendEnter: v.sendEnter !== false,
|
|
45
|
+
mode: v.mode,
|
|
46
|
+
// Keep the value while another mode is selected so switching back to
|
|
47
|
+
// cooldown does not silently replace the user's interval.
|
|
48
|
+
cooldownMs: v.cooldownMs == null ? 500 : v.cooldownMs
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default function TriggerForm ({ form, initial }) {
|
|
53
|
+
const actionType = Form.useWatch('actionType', form)
|
|
54
|
+
const mode = Form.useWatch('mode', form)
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
form.resetFields()
|
|
58
|
+
form.setFieldsValue({
|
|
59
|
+
name: initial.name || '',
|
|
60
|
+
enabled: initial.enabled !== false,
|
|
61
|
+
matchType: initial.match?.type || 'text',
|
|
62
|
+
matchValue: initial.match?.value || '',
|
|
63
|
+
caseSensitive: !!initial.match?.caseSensitive,
|
|
64
|
+
actionType: initial.action?.type || 'send',
|
|
65
|
+
actionValue: initial.action?.value || '',
|
|
66
|
+
sendEnter: initial.sendEnter !== false,
|
|
67
|
+
mode: initial.mode || 'cooldown',
|
|
68
|
+
cooldownMs: initial.cooldownMs == null ? 500 : initial.cooldownMs
|
|
69
|
+
})
|
|
70
|
+
}, [form, initial])
|
|
71
|
+
|
|
72
|
+
const presetMenu = {
|
|
73
|
+
items: triggerPresets.map((p, i) => ({
|
|
74
|
+
key: String(i),
|
|
75
|
+
label: p.name
|
|
76
|
+
})),
|
|
77
|
+
onClick: ({ key }) => {
|
|
78
|
+
const p = triggerPresets[Number(key)]
|
|
79
|
+
if (!p) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
form.setFieldsValue({
|
|
83
|
+
name: p.name,
|
|
84
|
+
enabled: true,
|
|
85
|
+
matchType: p.match.type,
|
|
86
|
+
matchValue: p.match.value,
|
|
87
|
+
caseSensitive: !!p.match.caseSensitive,
|
|
88
|
+
actionType: p.action.type,
|
|
89
|
+
actionValue: p.action.value,
|
|
90
|
+
sendEnter: p.sendEnter !== false,
|
|
91
|
+
mode: p.mode || 'cooldown',
|
|
92
|
+
cooldownMs: p.cooldownMs == null ? 500 : p.cooldownMs
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Form
|
|
99
|
+
form={form}
|
|
100
|
+
layout='vertical'
|
|
101
|
+
>
|
|
102
|
+
<div className='pd1b alignright'>
|
|
103
|
+
<Dropdown menu={presetMenu} trigger={['click']}>
|
|
104
|
+
<Button icon={<CaretDownOutlined />}>
|
|
105
|
+
{e('presets')}
|
|
106
|
+
</Button>
|
|
107
|
+
</Dropdown>
|
|
108
|
+
</div>
|
|
109
|
+
<Form.Item
|
|
110
|
+
name='name'
|
|
111
|
+
label={e('title')}
|
|
112
|
+
rules={[{ required: true, message: e('title') + ' required' }]}
|
|
113
|
+
>
|
|
114
|
+
<Input placeholder='Cisco --More-- auto pager' />
|
|
115
|
+
</Form.Item>
|
|
116
|
+
<Form.Item
|
|
117
|
+
name='enabled'
|
|
118
|
+
label={e('enabled')}
|
|
119
|
+
valuePropName='checked'
|
|
120
|
+
>
|
|
121
|
+
<Switch />
|
|
122
|
+
</Form.Item>
|
|
123
|
+
<Form.Item label='Match'>
|
|
124
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
125
|
+
<Form.Item name='matchType' noStyle>
|
|
126
|
+
<Radio.Group
|
|
127
|
+
options={triggerMatchTypes.map(t => ({ label: t.label, value: t.value }))}
|
|
128
|
+
optionType='button'
|
|
129
|
+
buttonStyle='solid'
|
|
130
|
+
size='small'
|
|
131
|
+
/>
|
|
132
|
+
</Form.Item>
|
|
133
|
+
<Form.Item
|
|
134
|
+
name='matchValue'
|
|
135
|
+
noStyle
|
|
136
|
+
rules={[{ required: true, message: 'match required' }]}
|
|
137
|
+
>
|
|
138
|
+
<Input
|
|
139
|
+
placeholder='--More-- or press any key.*'
|
|
140
|
+
style={{ flex: 1 }}
|
|
141
|
+
/>
|
|
142
|
+
</Form.Item>
|
|
143
|
+
</div>
|
|
144
|
+
</Form.Item>
|
|
145
|
+
<Form.Item
|
|
146
|
+
name='caseSensitive'
|
|
147
|
+
label='Case sensitive'
|
|
148
|
+
valuePropName='checked'
|
|
149
|
+
>
|
|
150
|
+
<Switch size='small' />
|
|
151
|
+
</Form.Item>
|
|
152
|
+
<Form.Item label='Action'>
|
|
153
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
154
|
+
<Form.Item name='actionType' noStyle>
|
|
155
|
+
<Select
|
|
156
|
+
style={{ width: 130 }}
|
|
157
|
+
options={triggerActionTypes.map(t => ({ label: t.label, value: t.value }))}
|
|
158
|
+
/>
|
|
159
|
+
</Form.Item>
|
|
160
|
+
<Form.Item name='actionValue' noStyle>
|
|
161
|
+
<Input
|
|
162
|
+
placeholder={actionType === 'notify' ? 'Notify only (no send)' : 'space, y, \\r, \\x03, ^M'}
|
|
163
|
+
style={{ flex: 1 }}
|
|
164
|
+
disabled={actionType === 'notify'}
|
|
165
|
+
/>
|
|
166
|
+
</Form.Item>
|
|
167
|
+
</div>
|
|
168
|
+
</Form.Item>
|
|
169
|
+
<div style={{ display: 'flex', gap: 24 }}>
|
|
170
|
+
<Form.Item
|
|
171
|
+
name='sendEnter'
|
|
172
|
+
label='Send Enter'
|
|
173
|
+
valuePropName='checked'
|
|
174
|
+
>
|
|
175
|
+
<Switch size='small' disabled={actionType === 'notify'} />
|
|
176
|
+
</Form.Item>
|
|
177
|
+
<Form.Item
|
|
178
|
+
name='mode'
|
|
179
|
+
label={e('mode')}
|
|
180
|
+
>
|
|
181
|
+
<Select
|
|
182
|
+
style={{ width: 190 }}
|
|
183
|
+
options={triggerModes.map(t => ({ label: t.label, value: t.value }))}
|
|
184
|
+
/>
|
|
185
|
+
</Form.Item>
|
|
186
|
+
<Form.Item
|
|
187
|
+
name='cooldownMs'
|
|
188
|
+
label='Cooldown (ms)'
|
|
189
|
+
>
|
|
190
|
+
<InputNumber
|
|
191
|
+
min={0}
|
|
192
|
+
max={600000}
|
|
193
|
+
step={100}
|
|
194
|
+
disabled={mode !== 'cooldown'}
|
|
195
|
+
/>
|
|
196
|
+
</Form.Item>
|
|
197
|
+
</div>
|
|
198
|
+
</Form>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger popup opened from the footer icon.
|
|
3
|
+
* Global tab: all predefined triggers, switches change the global on/off
|
|
4
|
+
* default (persisted in db, synced).
|
|
5
|
+
* This-session tab: ALL triggers (predefined + session temp/bookmark rules)
|
|
6
|
+
* with session-only switches (memory, per tab), plus a temp rule editor.
|
|
7
|
+
* Predefined trigger management (create/edit) lives in the settings panel.
|
|
8
|
+
*/
|
|
9
|
+
import { auto } from 'manate/react'
|
|
10
|
+
import { Modal, Tabs, Alert, Switch, Button, Empty, Tag } from 'antd'
|
|
11
|
+
import { SettingOutlined } from '@ant-design/icons'
|
|
12
|
+
import TriggerEditor, { matchSummary, actionSummary } from './trigger-editor.jsx'
|
|
13
|
+
import message from '../common/message'
|
|
14
|
+
|
|
15
|
+
const e = window.translate
|
|
16
|
+
|
|
17
|
+
function renderItemWrap (children) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className='trigger-item pd1x pd1y mg1b'
|
|
21
|
+
style={{ border: '1px solid var(--border, #333)', borderRadius: 4 }}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderMeta (t) {
|
|
29
|
+
return (
|
|
30
|
+
<div className='mg1t small muted elli' title={matchSummary(t) + ' → ' + actionSummary(t)}>
|
|
31
|
+
{matchSummary(t)} → {actionSummary(t)}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default auto(function TriggerSessionModal (props) {
|
|
37
|
+
const { store } = props
|
|
38
|
+
const open = !!store.triggerSessionOpen
|
|
39
|
+
if (!open) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
const tab = store.currentTab
|
|
43
|
+
const tabId = tab?.id || store.activeTabId
|
|
44
|
+
const sessionTriggers = (tab && tab.triggers) || []
|
|
45
|
+
const predefined = store.triggers || []
|
|
46
|
+
const overrides = (tab && tab.triggerOverrides) || {}
|
|
47
|
+
|
|
48
|
+
const handleSessionChange = (next) => {
|
|
49
|
+
const errors = store.setSessionTriggers(tabId, next)
|
|
50
|
+
if (errors && errors.length) {
|
|
51
|
+
message.error(errors[0])
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleClose = () => {
|
|
56
|
+
store.toggleTriggerSessionModal(false)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const openManage = () => {
|
|
60
|
+
store.toggleTriggerSessionModal(false)
|
|
61
|
+
store.openTriggers()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// global tab: switch = global on/off default (persisted)
|
|
65
|
+
const renderGlobalRow = (t) => {
|
|
66
|
+
return renderItemWrap(
|
|
67
|
+
<div>
|
|
68
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
69
|
+
<Switch
|
|
70
|
+
size='small'
|
|
71
|
+
checked={t.enabled !== false}
|
|
72
|
+
onChange={v => store.editTrigger(t.id, { enabled: v })}
|
|
73
|
+
/>
|
|
74
|
+
<b className='elli' style={{ flex: 1 }} title={t.name}>
|
|
75
|
+
{t.name || 'Unnamed'}
|
|
76
|
+
</b>
|
|
77
|
+
<Tag>{t.mode || 'cooldown'}</Tag>
|
|
78
|
+
</div>
|
|
79
|
+
{renderMeta(t)}
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// session tab: switch = on/off for this session only (memory)
|
|
85
|
+
const renderSessionRow = (t) => {
|
|
86
|
+
const globalOn = t.enabled !== false
|
|
87
|
+
const overridden = t.id in overrides
|
|
88
|
+
const checked = overridden ? overrides[t.id] : globalOn
|
|
89
|
+
return renderItemWrap(
|
|
90
|
+
<div>
|
|
91
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
92
|
+
<Switch
|
|
93
|
+
size='small'
|
|
94
|
+
checked={checked}
|
|
95
|
+
onChange={v => store.togglePredefinedTrigger(tabId, t.id, v)}
|
|
96
|
+
/>
|
|
97
|
+
<b className='elli' style={{ flex: 1 }} title={t.name}>
|
|
98
|
+
{t.name || 'Unnamed'}
|
|
99
|
+
</b>
|
|
100
|
+
{
|
|
101
|
+
overridden && overrides[t.id] !== globalOn
|
|
102
|
+
? (
|
|
103
|
+
<span className='small muted'>
|
|
104
|
+
{globalOn ? 'global: on' : 'global: off'}
|
|
105
|
+
</span>
|
|
106
|
+
)
|
|
107
|
+
: null
|
|
108
|
+
}
|
|
109
|
+
<Tag>{t.mode || 'cooldown'}</Tag>
|
|
110
|
+
</div>
|
|
111
|
+
{renderMeta(t)}
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const renderGlobalList = () => {
|
|
117
|
+
return (
|
|
118
|
+
<div>
|
|
119
|
+
<Alert
|
|
120
|
+
type='info'
|
|
121
|
+
showIcon
|
|
122
|
+
className='mg1b'
|
|
123
|
+
message='All predefined triggers. Switches change the global on/off default.'
|
|
124
|
+
/>
|
|
125
|
+
{
|
|
126
|
+
!predefined.length
|
|
127
|
+
? <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description='No triggers yet — add one in Manage' />
|
|
128
|
+
: predefined.map(renderGlobalRow)
|
|
129
|
+
}
|
|
130
|
+
<div className='pd1t' style={{ textAlign: 'right' }}>
|
|
131
|
+
<Button
|
|
132
|
+
size='small'
|
|
133
|
+
icon={<SettingOutlined />}
|
|
134
|
+
onClick={openManage}
|
|
135
|
+
>
|
|
136
|
+
{e('edit')}
|
|
137
|
+
</Button>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const renderSessionList = () => {
|
|
144
|
+
return (
|
|
145
|
+
<div>
|
|
146
|
+
{
|
|
147
|
+
!predefined.length
|
|
148
|
+
? null
|
|
149
|
+
: (
|
|
150
|
+
<div className='mg1b'>
|
|
151
|
+
<b>Predefined</b>
|
|
152
|
+
<span className='small muted mg1l'>Switches apply to this session only.</span>
|
|
153
|
+
<div className='pd1t'>
|
|
154
|
+
{predefined.map(renderSessionRow)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
<div className='mg1b'>
|
|
160
|
+
<b>This session</b>
|
|
161
|
+
<span className='small muted mg1l'>Temporary rules are not saved to the triggers database.</span>
|
|
162
|
+
</div>
|
|
163
|
+
<TriggerEditor
|
|
164
|
+
value={sessionTriggers}
|
|
165
|
+
onChange={handleSessionChange}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const activeCount = tab
|
|
172
|
+
? store.getEffectiveTriggers(tab).length
|
|
173
|
+
: predefined.filter(t => t.enabled !== false).length
|
|
174
|
+
|
|
175
|
+
const items = [
|
|
176
|
+
{
|
|
177
|
+
key: 'predefined',
|
|
178
|
+
label: `${e('global')} (${predefined.length})`,
|
|
179
|
+
children: renderGlobalList()
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
key: 'session',
|
|
183
|
+
label: `This session (${activeCount})`,
|
|
184
|
+
children: renderSessionList()
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Modal
|
|
190
|
+
open={open}
|
|
191
|
+
title={e('triggers')}
|
|
192
|
+
onCancel={handleClose}
|
|
193
|
+
footer={null}
|
|
194
|
+
width={680}
|
|
195
|
+
>
|
|
196
|
+
<Tabs items={items} />
|
|
197
|
+
</Modal>
|
|
198
|
+
)
|
|
199
|
+
})
|