@electerm/electerm-react 5.5.35 → 5.5.66
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/client/common/bookmark-schemas.js +1 -0
- package/client/common/constants.js +1 -0
- package/client/common/dangerous-session-fields.js +39 -0
- package/client/common/parse-quick-connect.js +47 -6
- package/client/common/parse-widget-log.js +57 -0
- package/client/common/safe-join.js +33 -0
- package/client/common/sanitize-filename.js +8 -0
- package/client/components/ai/agent-tool-call-card.jsx +7 -3
- package/client/components/ai/agent.js +176 -33
- package/client/components/ai/ai-auto-compress.js +119 -0
- package/client/components/ai/ai-chat-history-item.jsx +125 -40
- package/client/components/ai/ai-chat.jsx +67 -19
- package/client/components/ai/ai-compress.js +95 -0
- package/client/components/ai/ai-config.jsx +29 -16
- package/client/components/ai/ai-config.styl +20 -0
- package/client/components/ai/ai-context.js +1 -1
- package/client/components/ai/ai-output.jsx +1 -6
- package/client/components/ai/ai-presets.js +0 -15
- package/client/components/batch-op/batch-op-runner.jsx +1 -0
- package/client/components/bookmark-form/bookmark-schema.js +1 -0
- package/client/components/bookmark-form/common/ssh-agent.jsx +31 -21
- package/client/components/bookmark-form/config/ssh.js +1 -0
- package/client/components/bookmark-form/fix-bookmark-default.js +1 -0
- package/client/components/file-transfer/remote2remote-handlers.jsx +8 -3
- package/client/components/file-transfer/transfer.jsx +3 -119
- package/client/components/main/main.jsx +1 -0
- package/client/components/session/session-control.jsx +35 -2
- package/client/components/session/session-control.styl +5 -0
- package/client/components/session/session.jsx +13 -0
- package/client/components/setting-panel/setting-terminal.jsx +1 -0
- package/client/components/setting-panel/tab-widgets.jsx +27 -7
- package/client/components/sftp/file-item.jsx +0 -13
- package/client/components/terminal/mixins/term-init.js +1 -0
- package/client/components/terminal/mixins/term-touch.js +18 -0
- package/client/components/terminal/shell.js +11 -3
- package/client/components/terminal/shortcut-bar-press.js +192 -0
- package/client/components/terminal/shortcut-bar.jsx +199 -4
- package/client/components/terminal/shortcut-bar.styl +40 -21
- package/client/components/terminal/terminal.jsx +4 -0
- package/client/components/widgets/widget-control.jsx +4 -2
- package/client/components/widgets/widget-instance-detail.jsx +370 -0
- package/client/components/widgets/widget-instance.jsx +30 -13
- package/client/components/widgets/widget-instances.jsx +3 -1
- package/client/components/widgets/widgets-list.jsx +8 -0
- package/client/components/widgets/widgets.styl +100 -0
- package/client/store/common.js +81 -23
- package/client/store/init-state.js +7 -0
- package/client/store/load-data.js +1 -0
- package/client/store/mcp-handler.js +8 -15
- package/client/store/sync.js +1 -0
- package/client/store/tab.js +2 -15
- package/client/store/widgets.js +23 -5
- package/package.json +1 -1
- package/client/components/file-transfer/zip.js +0 -42
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detail view for a widget instance: what it is, and what it has been doing.
|
|
3
|
+
*
|
|
4
|
+
* Shown in the right column of the widgets panel (Settings -> Widgets) when an
|
|
5
|
+
* instance is picked from the "Running instances" list, in place of the run
|
|
6
|
+
* form. The instance keeps this panel after it stops — being able to read the
|
|
7
|
+
* log of an instance that died is the whole point of it.
|
|
8
|
+
*
|
|
9
|
+
* Both panes come out of the instance's log file (see
|
|
10
|
+
* app/widgets/instance-log.js): on open, and again whenever Reload is pressed.
|
|
11
|
+
* Deliberately not on a timer — a log that scrolls under you is harder to read
|
|
12
|
+
* than one you refresh when you are ready.
|
|
13
|
+
*
|
|
14
|
+
* The strings here are English: the keys this panel needs do not exist yet, and
|
|
15
|
+
* a new key only reaches the app through an electerm-locales release plus a dep
|
|
16
|
+
* bump (see the locales notes). `e()` is used only for keys that do exist.
|
|
17
|
+
*/
|
|
18
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
19
|
+
import {
|
|
20
|
+
Alert,
|
|
21
|
+
Button,
|
|
22
|
+
Descriptions,
|
|
23
|
+
Popconfirm,
|
|
24
|
+
Segmented,
|
|
25
|
+
Space,
|
|
26
|
+
Tag,
|
|
27
|
+
Tooltip,
|
|
28
|
+
Typography
|
|
29
|
+
} from 'antd'
|
|
30
|
+
import {
|
|
31
|
+
CopyOutlined,
|
|
32
|
+
DeleteOutlined,
|
|
33
|
+
ReloadOutlined,
|
|
34
|
+
StopOutlined
|
|
35
|
+
} from '@ant-design/icons'
|
|
36
|
+
import { auto } from 'manate/react'
|
|
37
|
+
import { copy } from '../../common/clipboard'
|
|
38
|
+
import parseWidgetLog from '../../common/parse-widget-log'
|
|
39
|
+
import './widgets.styl'
|
|
40
|
+
|
|
41
|
+
const e = window.translate
|
|
42
|
+
|
|
43
|
+
const SECRET_KEY = /password|passwd|secret|token|apikey|api_key/i
|
|
44
|
+
const CONN_MARK = { ok: '✓', fail: '✗', info: '•' }
|
|
45
|
+
|
|
46
|
+
function pad (n, len = 2) {
|
|
47
|
+
return String(n).padStart(len, '0')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatTime (ts) {
|
|
51
|
+
const d = new Date(ts)
|
|
52
|
+
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// the URL of a widget that needs credentials carries them; the panel gets
|
|
56
|
+
// screenshotted, so hide the password here (copy still copies the real one)
|
|
57
|
+
function maskUrl (url) {
|
|
58
|
+
return String(url || '').replace(/\/\/([^:/@]+):([^@/]+)@/, '//$1:••••••@')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function displayValue (key, value) {
|
|
62
|
+
if (SECRET_KEY.test(key)) {
|
|
63
|
+
return value ? '••••••' : ''
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === 'boolean') {
|
|
66
|
+
return value ? 'true' : 'false'
|
|
67
|
+
}
|
|
68
|
+
return value === undefined || value === null ? '' : String(value)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default auto(function WidgetInstanceDetail ({ instance, store }) {
|
|
72
|
+
const [pane, setPane] = useState('log')
|
|
73
|
+
const [data, setData] = useState({ logs: [], events: [] })
|
|
74
|
+
// which confirm popup is open ('' | 'clear' | 'stop'), so its tooltip can
|
|
75
|
+
// step aside — both are anchored to the same button, and the tooltip would
|
|
76
|
+
// otherwise sit on top of the confirm button and swallow the click
|
|
77
|
+
const [confirming, setConfirming] = useState('')
|
|
78
|
+
const [status, setStatus] = useState({
|
|
79
|
+
loaded: false,
|
|
80
|
+
loading: false,
|
|
81
|
+
exists: true,
|
|
82
|
+
error: ''
|
|
83
|
+
})
|
|
84
|
+
const paneRef = useRef(null)
|
|
85
|
+
|
|
86
|
+
const running = store.widgetInstances.some(w => w.id === instance.id)
|
|
87
|
+
const logPath = store.widgetLogPath && window.reqs?.path?.join
|
|
88
|
+
? window.reqs.path.join(store.widgetLogPath, `${instance.id}.log`)
|
|
89
|
+
: ''
|
|
90
|
+
|
|
91
|
+
const readLog = useCallback(async () => {
|
|
92
|
+
if (!logPath) {
|
|
93
|
+
setStatus({ loaded: true, loading: false, exists: false, error: '' })
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
setStatus(s => ({ ...s, loading: true }))
|
|
97
|
+
try {
|
|
98
|
+
// read straight through: fs/promises has no `exists`, and the bridge
|
|
99
|
+
// rejects unknown helpers — so a missing file is detected from ENOENT
|
|
100
|
+
// rather than probed for, which also saves a round trip
|
|
101
|
+
const text = await window.fs.readFile(logPath)
|
|
102
|
+
setData(parseWidgetLog(text))
|
|
103
|
+
setStatus({ loaded: true, loading: false, exists: true, error: '' })
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const msg = (err && err.message) || String(err)
|
|
106
|
+
if (/ENOENT|no such file/i.test(msg)) {
|
|
107
|
+
// the normal first state of a fresh instance, not a failure
|
|
108
|
+
setData({ logs: [], events: [] })
|
|
109
|
+
setStatus({ loaded: true, loading: false, exists: false, error: '' })
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
setStatus({ loaded: true, loading: false, exists: true, error: msg })
|
|
113
|
+
}
|
|
114
|
+
}, [logPath])
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
readLog()
|
|
118
|
+
}, [readLog])
|
|
119
|
+
|
|
120
|
+
// newest entry is at the bottom, so land there after every load
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
const el = paneRef.current
|
|
123
|
+
if (el) {
|
|
124
|
+
el.scrollTop = el.scrollHeight
|
|
125
|
+
}
|
|
126
|
+
}, [data, pane])
|
|
127
|
+
|
|
128
|
+
const clearLog = async () => {
|
|
129
|
+
if (!logPath) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
// the bridge resolves false instead of rejecting when the write fails
|
|
134
|
+
const ok = await window.fs.writeFile(logPath, '')
|
|
135
|
+
if (ok === false) {
|
|
136
|
+
throw new Error('could not write ' + logPath)
|
|
137
|
+
}
|
|
138
|
+
setData({ logs: [], events: [] })
|
|
139
|
+
setStatus(s => ({ ...s, exists: true, error: '' }))
|
|
140
|
+
} catch (err) {
|
|
141
|
+
setStatus(s => ({ ...s, error: err.message || String(err) }))
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Stopping is the one log change the user causes directly, so refresh after
|
|
146
|
+
// it — that is not the polling the panel is avoiding, it is the panel
|
|
147
|
+
// agreeing with the button that was just pressed.
|
|
148
|
+
const stopInstance = async () => {
|
|
149
|
+
await store.stopWidget(instance.id)
|
|
150
|
+
readLog()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const infoItems = [
|
|
154
|
+
{
|
|
155
|
+
key: 'widget',
|
|
156
|
+
label: 'Widget',
|
|
157
|
+
children: instance.widgetId
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
key: 'instance',
|
|
161
|
+
label: 'Instance',
|
|
162
|
+
children: <span className='wil-mono'>{instance.id}</span>
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
key: 'status',
|
|
166
|
+
label: 'Status',
|
|
167
|
+
children: running ? <Tag color='green'>running</Tag> : <Tag>stopped</Tag>
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
if (instance.serverInfo && instance.serverInfo.url) {
|
|
171
|
+
infoItems.push({
|
|
172
|
+
key: 'url',
|
|
173
|
+
label: 'URL',
|
|
174
|
+
children: (
|
|
175
|
+
<span className='wil-mono' title={maskUrl(instance.serverInfo.url)}>
|
|
176
|
+
{maskUrl(instance.serverInfo.url)}
|
|
177
|
+
</span>
|
|
178
|
+
)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
if (instance.serverInfo && instance.serverInfo.path) {
|
|
182
|
+
infoItems.push({
|
|
183
|
+
key: 'path',
|
|
184
|
+
label: 'Path',
|
|
185
|
+
children: <span className='wil-mono'>{instance.serverInfo.path}</span>
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
if (instance.startedAt) {
|
|
189
|
+
infoItems.push({
|
|
190
|
+
key: 'startedAt',
|
|
191
|
+
label: 'Started',
|
|
192
|
+
children: new Date(instance.startedAt).toLocaleString()
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
for (const key of Object.keys(instance.config || {})) {
|
|
196
|
+
if (key === 'autoRun') {
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
infoItems.push({
|
|
200
|
+
key: 'config-' + key,
|
|
201
|
+
label: key,
|
|
202
|
+
children: <span className='wil-mono'>{displayValue(key, instance.config[key])}</span>
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let emptyText = 'Nothing logged yet. Press reload after the widget has been used.'
|
|
207
|
+
if (!logPath) {
|
|
208
|
+
emptyText = 'The widget log directory is not available in this environment.'
|
|
209
|
+
} else if (!status.exists) {
|
|
210
|
+
emptyText = 'No log file yet — this instance has not written anything.'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const renderPane = () => {
|
|
214
|
+
const isLog = pane === 'log'
|
|
215
|
+
const entries = isLog ? data.logs : data.events
|
|
216
|
+
return (
|
|
217
|
+
<>
|
|
218
|
+
{
|
|
219
|
+
status.error
|
|
220
|
+
? (
|
|
221
|
+
<Alert
|
|
222
|
+
className='mg1y'
|
|
223
|
+
type='error'
|
|
224
|
+
showIcon
|
|
225
|
+
message={status.error}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
: null
|
|
229
|
+
}
|
|
230
|
+
<div className='wil-pane' ref={paneRef}>
|
|
231
|
+
{entries.map((entry, i) => {
|
|
232
|
+
const state = isLog
|
|
233
|
+
? entry.level
|
|
234
|
+
: (entry.ok === null ? 'info' : (entry.ok ? 'ok' : 'fail'))
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
key={i}
|
|
238
|
+
className={'wil-line wil-' + state}
|
|
239
|
+
>
|
|
240
|
+
<span
|
|
241
|
+
className='wil-time'
|
|
242
|
+
title={new Date(entry.ts).toISOString()}
|
|
243
|
+
>
|
|
244
|
+
{formatTime(entry.ts)}
|
|
245
|
+
</span>
|
|
246
|
+
{isLog
|
|
247
|
+
? null
|
|
248
|
+
: <span className='wil-mark'>{CONN_MARK[state]}</span>}
|
|
249
|
+
{isLog ? null : <span className='wil-type'>{entry.type}</span>}
|
|
250
|
+
{isLog || !entry.from
|
|
251
|
+
? null
|
|
252
|
+
: <span className='wil-from'>{entry.from}</span>}
|
|
253
|
+
<span className='wil-msg'>{entry.msg}</span>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
})}
|
|
257
|
+
{entries.length ? null : <div className='wil-empty'>{emptyText}</div>}
|
|
258
|
+
</div>
|
|
259
|
+
{logPath
|
|
260
|
+
? (
|
|
261
|
+
<div className='wil-path elli' title={logPath}>
|
|
262
|
+
Log file: {logPath}
|
|
263
|
+
</div>
|
|
264
|
+
)
|
|
265
|
+
: null}
|
|
266
|
+
</>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<div className='widget-instance-detail'>
|
|
272
|
+
<div className='wil-head'>
|
|
273
|
+
<Typography.Text
|
|
274
|
+
strong
|
|
275
|
+
ellipsis
|
|
276
|
+
className='wil-head-title'
|
|
277
|
+
title={instance.title}
|
|
278
|
+
>
|
|
279
|
+
{instance.title}
|
|
280
|
+
</Typography.Text>
|
|
281
|
+
<Space size={4}>
|
|
282
|
+
<Tooltip title='Reload log'>
|
|
283
|
+
<Button
|
|
284
|
+
size='small'
|
|
285
|
+
icon={<ReloadOutlined />}
|
|
286
|
+
loading={status.loading}
|
|
287
|
+
onClick={readLog}
|
|
288
|
+
/>
|
|
289
|
+
</Tooltip>
|
|
290
|
+
{instance.serverInfo && instance.serverInfo.url
|
|
291
|
+
? (
|
|
292
|
+
<Tooltip title='Copy URL'>
|
|
293
|
+
<Button
|
|
294
|
+
size='small'
|
|
295
|
+
icon={<CopyOutlined />}
|
|
296
|
+
onClick={() => copy(instance.serverInfo.url)}
|
|
297
|
+
/>
|
|
298
|
+
</Tooltip>
|
|
299
|
+
)
|
|
300
|
+
: null}
|
|
301
|
+
{logPath
|
|
302
|
+
? (
|
|
303
|
+
<Popconfirm
|
|
304
|
+
title='Clear this instance log?'
|
|
305
|
+
okText='Clear'
|
|
306
|
+
cancelText={e('cancel')}
|
|
307
|
+
onOpenChange={open => setConfirming(open ? 'clear' : '')}
|
|
308
|
+
onConfirm={clearLog}
|
|
309
|
+
>
|
|
310
|
+
<Tooltip title='Clear log' open={confirming === 'clear' ? false : undefined}>
|
|
311
|
+
<Button size='small' icon={<DeleteOutlined />} />
|
|
312
|
+
</Tooltip>
|
|
313
|
+
</Popconfirm>
|
|
314
|
+
)
|
|
315
|
+
: null}
|
|
316
|
+
{running
|
|
317
|
+
? (
|
|
318
|
+
<Popconfirm
|
|
319
|
+
title='Stop this widget?'
|
|
320
|
+
okText='Stop'
|
|
321
|
+
cancelText={e('cancel')}
|
|
322
|
+
onOpenChange={open => setConfirming(open ? 'stop' : '')}
|
|
323
|
+
onConfirm={stopInstance}
|
|
324
|
+
>
|
|
325
|
+
<Tooltip title='Stop' open={confirming === 'stop' ? false : undefined}>
|
|
326
|
+
<Button
|
|
327
|
+
size='small'
|
|
328
|
+
danger
|
|
329
|
+
icon={<StopOutlined />}
|
|
330
|
+
/>
|
|
331
|
+
</Tooltip>
|
|
332
|
+
</Popconfirm>
|
|
333
|
+
)
|
|
334
|
+
: (
|
|
335
|
+
<Button
|
|
336
|
+
size='small'
|
|
337
|
+
onClick={() => store.closeWidgetInstance()}
|
|
338
|
+
>
|
|
339
|
+
Back
|
|
340
|
+
</Button>
|
|
341
|
+
)}
|
|
342
|
+
</Space>
|
|
343
|
+
</div>
|
|
344
|
+
<Descriptions
|
|
345
|
+
className='wil-descriptions'
|
|
346
|
+
size='small'
|
|
347
|
+
bordered
|
|
348
|
+
column={1}
|
|
349
|
+
items={infoItems}
|
|
350
|
+
/>
|
|
351
|
+
<Segmented
|
|
352
|
+
className='wil-switch'
|
|
353
|
+
size='small'
|
|
354
|
+
value={pane}
|
|
355
|
+
onChange={setPane}
|
|
356
|
+
options={[
|
|
357
|
+
{
|
|
358
|
+
label: `Running log (${data.logs.length})`,
|
|
359
|
+
value: 'log'
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
label: `Connections (${data.events.length})`,
|
|
363
|
+
value: 'conn'
|
|
364
|
+
}
|
|
365
|
+
]}
|
|
366
|
+
/>
|
|
367
|
+
{renderPane()}
|
|
368
|
+
</div>
|
|
369
|
+
)
|
|
370
|
+
})
|
|
@@ -11,10 +11,11 @@ import { auto } from 'manate/react'
|
|
|
11
11
|
|
|
12
12
|
const e = window.translate
|
|
13
13
|
|
|
14
|
-
export default auto(function WidgetInstance ({ item }) {
|
|
14
|
+
export default auto(function WidgetInstance ({ item, active, onClick }) {
|
|
15
15
|
const { id, title, serverInfo, autoRun } = item
|
|
16
16
|
const cls = classnames('item-list-unit', {
|
|
17
|
-
'autorun-active': autoRun
|
|
17
|
+
'autorun-active': autoRun,
|
|
18
|
+
active
|
|
18
19
|
})
|
|
19
20
|
const delProps = {
|
|
20
21
|
title: e('del'),
|
|
@@ -28,6 +29,16 @@ export default auto(function WidgetInstance ({ item }) {
|
|
|
28
29
|
function onConfirm () {
|
|
29
30
|
window.store.stopWidget(id)
|
|
30
31
|
}
|
|
32
|
+
// the row itself opens the instance detail, so the action icons must not
|
|
33
|
+
// also trigger it
|
|
34
|
+
function stopPropagation (evt) {
|
|
35
|
+
evt.stopPropagation()
|
|
36
|
+
}
|
|
37
|
+
function handleClick () {
|
|
38
|
+
if (onClick) {
|
|
39
|
+
onClick(item)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
31
42
|
const popProps = {
|
|
32
43
|
title: e('del') + '?',
|
|
33
44
|
onConfirm,
|
|
@@ -45,7 +56,7 @@ export default auto(function WidgetInstance ({ item }) {
|
|
|
45
56
|
}
|
|
46
57
|
const popoverContent = serverInfo
|
|
47
58
|
? (
|
|
48
|
-
<div>
|
|
59
|
+
<div className='wil-pop-info'>
|
|
49
60
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
50
61
|
<span>URL: {serverInfo.url}</span>
|
|
51
62
|
<CopyOutlined
|
|
@@ -70,6 +81,7 @@ export default auto(function WidgetInstance ({ item }) {
|
|
|
70
81
|
<div
|
|
71
82
|
key={id}
|
|
72
83
|
className={cls}
|
|
84
|
+
onClick={handleClick}
|
|
73
85
|
>
|
|
74
86
|
{
|
|
75
87
|
serverInfo
|
|
@@ -84,17 +96,22 @@ export default auto(function WidgetInstance ({ item }) {
|
|
|
84
96
|
)
|
|
85
97
|
: titleDiv
|
|
86
98
|
}
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
onClick={handleToggleAutoRun}
|
|
91
|
-
/>
|
|
92
|
-
</Tooltip>
|
|
93
|
-
<Popconfirm
|
|
94
|
-
{...popProps}
|
|
99
|
+
<div
|
|
100
|
+
className='wil-item-actions'
|
|
101
|
+
onClick={stopPropagation}
|
|
95
102
|
>
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
<Tooltip title='Toggle auto-run'>
|
|
104
|
+
<ThunderboltOutlined
|
|
105
|
+
className='pointer list-item-autorun'
|
|
106
|
+
onClick={handleToggleAutoRun}
|
|
107
|
+
/>
|
|
108
|
+
</Tooltip>
|
|
109
|
+
<Popconfirm
|
|
110
|
+
{...popProps}
|
|
111
|
+
>
|
|
112
|
+
{icon}
|
|
113
|
+
</Popconfirm>
|
|
114
|
+
</div>
|
|
98
115
|
</div>
|
|
99
116
|
)
|
|
100
117
|
})
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import WidgetInstance from './widget-instance'
|
|
2
2
|
|
|
3
|
-
export default function WidgetInstances ({ widgetInstances }) {
|
|
3
|
+
export default function WidgetInstances ({ widgetInstances, activeItemId, onClickItem }) {
|
|
4
4
|
return widgetInstances.map(item => (
|
|
5
5
|
<WidgetInstance
|
|
6
6
|
key={item.id}
|
|
7
7
|
item={item}
|
|
8
|
+
active={activeItemId === item.id}
|
|
9
|
+
onClick={onClickItem}
|
|
8
10
|
/>
|
|
9
11
|
))
|
|
10
12
|
}
|
|
@@ -138,10 +138,18 @@ export default auto(function WidgetsList ({ activeItemId, store, onClickItem })
|
|
|
138
138
|
)
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// clicking a running instance shows its detail + log in the right column
|
|
142
|
+
// (the widgets list above drills into the run form instead)
|
|
143
|
+
const onClickWidgetInstance = (instance) => {
|
|
144
|
+
window.store.selectWidgetInstance(instance)
|
|
145
|
+
}
|
|
146
|
+
|
|
141
147
|
const renderInstancesSection = () => {
|
|
142
148
|
return (
|
|
143
149
|
<WidgetInstances
|
|
144
150
|
widgetInstances={widgetInstances}
|
|
151
|
+
activeItemId={activeItemId}
|
|
152
|
+
onClickItem={onClickWidgetInstance}
|
|
145
153
|
/>
|
|
146
154
|
)
|
|
147
155
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
// hover popover on a running-instance row. A long path makes it wider than the
|
|
3
|
+
// list column, and it then spills over the detail panel's heading, so it is
|
|
4
|
+
// held to the column and wrapped instead.
|
|
5
|
+
.wil-pop-info
|
|
6
|
+
max-width 220px
|
|
7
|
+
word-break break-all
|
|
8
|
+
|
|
9
|
+
// widget manager: instance detail view (widget-instance-detail.jsx)
|
|
10
|
+
.widget-instance-detail
|
|
11
|
+
display flex
|
|
12
|
+
flex-direction column
|
|
13
|
+
min-height 0
|
|
14
|
+
height 100%
|
|
15
|
+
.wil-head
|
|
16
|
+
display flex
|
|
17
|
+
align-items center
|
|
18
|
+
justify-content space-between
|
|
19
|
+
gap 8px
|
|
20
|
+
margin-bottom 10px
|
|
21
|
+
flex-shrink 0
|
|
22
|
+
.wil-head-title
|
|
23
|
+
min-width 0
|
|
24
|
+
font-size 14px
|
|
25
|
+
.wil-descriptions
|
|
26
|
+
// the log is the point of this panel, so a long config list gives up room
|
|
27
|
+
// (and scrolls) instead of squeezing it: shrinkable, and allowed to go below
|
|
28
|
+
// its content height, with the pane holding a floor of its own
|
|
29
|
+
flex 0 1 auto
|
|
30
|
+
min-height 0
|
|
31
|
+
overflow auto
|
|
32
|
+
// long values (paths, URLs, keys) must wrap instead of widening the panel
|
|
33
|
+
.ant-descriptions-item-label
|
|
34
|
+
width 90px
|
|
35
|
+
white-space nowrap
|
|
36
|
+
.ant-descriptions-item-content
|
|
37
|
+
word-break break-all
|
|
38
|
+
.wil-mono
|
|
39
|
+
font-family monospace
|
|
40
|
+
font-size 12px
|
|
41
|
+
.wil-switch
|
|
42
|
+
margin 10px 0 6px
|
|
43
|
+
flex-shrink 0
|
|
44
|
+
.wil-pane
|
|
45
|
+
flex 1
|
|
46
|
+
// a floor, so the log stays readable no matter how long the config list is
|
|
47
|
+
min-height 200px
|
|
48
|
+
overflow auto
|
|
49
|
+
padding 6px 8px
|
|
50
|
+
background var(--main-dark)
|
|
51
|
+
border 1px solid var(--main-light)
|
|
52
|
+
border-radius 4px
|
|
53
|
+
font-family monospace
|
|
54
|
+
font-size 12px
|
|
55
|
+
line-height 1.6
|
|
56
|
+
.wil-line
|
|
57
|
+
display flex
|
|
58
|
+
gap 6px
|
|
59
|
+
white-space pre-wrap
|
|
60
|
+
word-break break-word
|
|
61
|
+
border-bottom 1px solid var(--main-light)
|
|
62
|
+
&:last-child
|
|
63
|
+
border-bottom 0
|
|
64
|
+
.wil-time
|
|
65
|
+
flex-shrink 0
|
|
66
|
+
color var(--text-disabled)
|
|
67
|
+
.wil-mark
|
|
68
|
+
.wil-type
|
|
69
|
+
flex-shrink 0
|
|
70
|
+
.wil-from
|
|
71
|
+
flex-shrink 0
|
|
72
|
+
color var(--text-dark)
|
|
73
|
+
.wil-msg
|
|
74
|
+
min-width 0
|
|
75
|
+
.wil-debug
|
|
76
|
+
color var(--text-dark)
|
|
77
|
+
.wil-log
|
|
78
|
+
color var(--text)
|
|
79
|
+
.wil-warn
|
|
80
|
+
color var(--warn)
|
|
81
|
+
.wil-error
|
|
82
|
+
color var(--error)
|
|
83
|
+
.wil-ok
|
|
84
|
+
color var(--success)
|
|
85
|
+
.wil-mark
|
|
86
|
+
color var(--success)
|
|
87
|
+
.wil-fail
|
|
88
|
+
color var(--error)
|
|
89
|
+
.wil-mark
|
|
90
|
+
color var(--error)
|
|
91
|
+
.wil-info
|
|
92
|
+
color var(--text-dark)
|
|
93
|
+
.wil-empty
|
|
94
|
+
color var(--text-dark)
|
|
95
|
+
padding 6px 0
|
|
96
|
+
.wil-path
|
|
97
|
+
flex-shrink 0
|
|
98
|
+
margin-top 6px
|
|
99
|
+
color var(--text-disabled)
|
|
100
|
+
font-size 11px
|