@electerm/electerm-react 5.3.15 → 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 +23 -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/bookmark-form/index.jsx +10 -8
- 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 +80 -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/col.jsx +32 -6
- package/client/components/setting-panel/list.styl +12 -0
- package/client/components/setting-panel/setting-common.jsx +25 -56
- package/client/components/setting-panel/setting-mobile-breadcrumb.jsx +29 -0
- package/client/components/setting-panel/setting-modal.jsx +64 -44
- package/client/components/setting-panel/setting-terminal.jsx +7 -2
- package/client/components/setting-panel/setting-wrap.jsx +0 -3
- package/client/components/setting-panel/setting-wrap.styl +80 -11
- 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/sidebar/index.jsx +28 -21
- package/client/components/sidebar/left-sidebar-icons.jsx +1 -1
- package/client/components/sidebar/sidebar.styl +16 -0
- 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/tabs/tabs.styl +4 -0
- 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 +32 -27
- 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/theme/theme-form.jsx +22 -0
- package/client/components/tree-list/bookmark-transport.jsx +7 -31
- package/client/components/tree-list/tree-list.styl +13 -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 +14 -0
- package/client/store/mcp-handler.js +158 -30
- package/client/store/setting.js +20 -0
- package/client/store/sidebar.js +12 -0
- package/client/store/store.js +2 -0
- package/client/store/sync.js +45 -9
- package/client/store/system-menu.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,458 @@
|
|
|
1
|
+
import { auto } from 'manate/react'
|
|
2
|
+
import { Button, Popconfirm, Table } from 'antd'
|
|
3
|
+
import { CloseCircleOutlined, BarChartOutlined } from '@ant-design/icons'
|
|
4
|
+
import { formatBytes, formatDuration, formatRate, groupOf, selectPrimaryNetwork, sortDisks } from './monitor-model'
|
|
5
|
+
import { copy } from '../../common/clipboard'
|
|
6
|
+
import './monitor-details.styl'
|
|
7
|
+
|
|
8
|
+
const e = window.translate
|
|
9
|
+
|
|
10
|
+
// store.isMobile is the global 600px rule (mobileBreakpoint in ../../common/constants.js),
|
|
11
|
+
// used instead of antd's own `responsive` breakpoints so there is a single source of truth.
|
|
12
|
+
const desktopOnly = columns => window.store.isMobile ? [] : columns
|
|
13
|
+
|
|
14
|
+
export function Sparkline ({ history, level = 'normal', large = false }) {
|
|
15
|
+
if (!history.length) {
|
|
16
|
+
return <span className='remote-monitor-empty'>—</span>
|
|
17
|
+
}
|
|
18
|
+
const width = large ? 360 : 72
|
|
19
|
+
const height = large ? 88 : 20
|
|
20
|
+
const start = history[0].timestamp
|
|
21
|
+
const end = history[history.length - 1].timestamp
|
|
22
|
+
const span = Math.max(1, end - start)
|
|
23
|
+
const segments = []
|
|
24
|
+
let current = []
|
|
25
|
+
history.forEach((sample, index) => {
|
|
26
|
+
if (index && sample.timestamp - history[index - 1].timestamp > 12000) {
|
|
27
|
+
if (current.length) segments.push(current)
|
|
28
|
+
current = []
|
|
29
|
+
}
|
|
30
|
+
const x = (sample.timestamp - start) * width / span
|
|
31
|
+
const y = height - Math.max(0, Math.min(100, sample.value)) * height / 100
|
|
32
|
+
current.push(`${x.toFixed(1)},${y.toFixed(1)}`)
|
|
33
|
+
})
|
|
34
|
+
if (current.length) segments.push(current)
|
|
35
|
+
return (
|
|
36
|
+
<svg
|
|
37
|
+
aria-label={`${e('cpuHistory')}: ${Math.round(history[history.length - 1].value)}%`}
|
|
38
|
+
className={`remote-monitor-sparkline remote-monitor-level-${level}`}
|
|
39
|
+
height={height}
|
|
40
|
+
role='img'
|
|
41
|
+
viewBox={`0 0 ${width} ${height}`}
|
|
42
|
+
width={width}
|
|
43
|
+
>
|
|
44
|
+
{
|
|
45
|
+
segments.map((points, index) => (
|
|
46
|
+
<polyline
|
|
47
|
+
fill='none'
|
|
48
|
+
key={`${points[0]}-${index}`}
|
|
49
|
+
points={points.join(' ')}
|
|
50
|
+
stroke='currentColor'
|
|
51
|
+
strokeLinecap='round'
|
|
52
|
+
strokeLinejoin='round'
|
|
53
|
+
strokeWidth={large ? 2 : 1.5}
|
|
54
|
+
/>
|
|
55
|
+
))
|
|
56
|
+
}
|
|
57
|
+
</svg>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function DetailRows ({ rows }) {
|
|
62
|
+
return (
|
|
63
|
+
<dl className='remote-monitor-detail-rows'>
|
|
64
|
+
{
|
|
65
|
+
rows.filter(row => row[1] !== null && row[1] !== undefined && row[1] !== '').map(row => (
|
|
66
|
+
<div className='remote-monitor-detail-row' key={row[0]}>
|
|
67
|
+
<dt>{row[0]}</dt>
|
|
68
|
+
<dd title={String(row[1])}>{row[1]}</dd>
|
|
69
|
+
</div>
|
|
70
|
+
))
|
|
71
|
+
}
|
|
72
|
+
</dl>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// antd Table only contains its own horizontal overflow when `scroll.x` is set: rc-table
|
|
77
|
+
// then wraps the content in an overflow-x:auto box and gives the inner table
|
|
78
|
+
// `width: x; min-width: 100%`. A *numeric* x (not 'max-content') keeps table-layout:
|
|
79
|
+
// fixed, so `ellipsis` columns still truncate and the width-less column soaks up the
|
|
80
|
+
// slack — 'max-content' would switch us back to auto layout and let a long process
|
|
81
|
+
// command stretch the table instead of ellipsising it.
|
|
82
|
+
// Without scroll.x the widest tables (network 460px, disks 452px) blow past the popover
|
|
83
|
+
// content box and drag the whole panel — heading and info button included — into a
|
|
84
|
+
// horizontal scrollbar. (`.remote-monitor-popover` is sized 488px so its 464px content
|
|
85
|
+
// box clears even the network table; scroll.x is the fallback for narrower widths.)
|
|
86
|
+
// Columns without a width are the flexible ones; give them a floor so x stays a
|
|
87
|
+
// realistic minimum instead of collapsing to just the fixed columns.
|
|
88
|
+
const FLEX_COLUMN_MIN_WIDTH = 100
|
|
89
|
+
|
|
90
|
+
const minTableWidth = columns => columns.reduce(
|
|
91
|
+
(sum, column) => sum + (typeof column.width === 'number' ? column.width : FLEX_COLUMN_MIN_WIDTH),
|
|
92
|
+
0
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// antd Table gives us column ellipsis and the compact size for free, so no
|
|
96
|
+
// hand rolled markup or table css is needed here.
|
|
97
|
+
function DetailTable ({ columns, rows, rowKey }) {
|
|
98
|
+
const mergedColumns = columns.map(withCellCopy)
|
|
99
|
+
return (
|
|
100
|
+
<Table
|
|
101
|
+
className='remote-monitor-table'
|
|
102
|
+
columns={mergedColumns}
|
|
103
|
+
dataSource={rows}
|
|
104
|
+
pagination={false}
|
|
105
|
+
rowKey={rowKey}
|
|
106
|
+
scroll={{ x: minTableWidth(mergedColumns) }}
|
|
107
|
+
size='small'
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Click a value cell to copy its text. Columns opt in with `copyValue(row)`, so the
|
|
113
|
+
// kill button and any other interactive cell stays untouched. `copy`() writes through
|
|
114
|
+
// window.pre.writeClipboard and fires the shared `copied` toast on its own.
|
|
115
|
+
// A finished text selection wins over the click, so drag selecting inside a cell
|
|
116
|
+
// never clobbers the clipboard by accident.
|
|
117
|
+
function withCellCopy (column) {
|
|
118
|
+
const { copyValue } = column
|
|
119
|
+
if (!copyValue) {
|
|
120
|
+
return column
|
|
121
|
+
}
|
|
122
|
+
const rest = { ...column }
|
|
123
|
+
delete rest.copyValue
|
|
124
|
+
return {
|
|
125
|
+
...rest,
|
|
126
|
+
onCell: row => ({
|
|
127
|
+
className: 'remote-monitor-copy-cell',
|
|
128
|
+
onClick: () => {
|
|
129
|
+
if (window.getSelection()?.toString()) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
copy(String(copyValue(row)))
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function GroupState ({ group }) {
|
|
139
|
+
if (group.data !== null && group.data !== undefined) {
|
|
140
|
+
if (group.status === 'stale') {
|
|
141
|
+
return (
|
|
142
|
+
<div className='remote-monitor-detail-state remote-monitor-level-warning'>
|
|
143
|
+
{e('stale')}: {new Date(group.updatedAt).toLocaleTimeString()}
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
const text = group.status === 'loading' || group.status === 'idle'
|
|
150
|
+
? e('loading')
|
|
151
|
+
: group.status === 'unsupported'
|
|
152
|
+
? e('unavailable')
|
|
153
|
+
: group.error || e('unavailable')
|
|
154
|
+
return <div className='remote-monitor-detail-state'>{text}</div>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function ActivityDetails ({ group, sortBy, onKillProcess }) {
|
|
158
|
+
const activities = Array.isArray(group.data) ? [...group.data] : []
|
|
159
|
+
activities.sort(sortBy === 'memory'
|
|
160
|
+
? (a, b) => b.memBytes - a.memBytes || b.cpu - a.cpu
|
|
161
|
+
: (a, b) => b.cpu - a.cpu || b.memBytes - a.memBytes)
|
|
162
|
+
const rows = sortBy ? activities.slice(0, 10) : activities
|
|
163
|
+
return (
|
|
164
|
+
<div className='remote-monitor-activity'>
|
|
165
|
+
{sortBy && <div className='remote-monitor-detail-heading'>{e('activity')}</div>}
|
|
166
|
+
<GroupState group={group} />
|
|
167
|
+
{
|
|
168
|
+
rows.length
|
|
169
|
+
? (
|
|
170
|
+
<DetailTable
|
|
171
|
+
columns={[
|
|
172
|
+
...(onKillProcess
|
|
173
|
+
? [{
|
|
174
|
+
key: 'kill',
|
|
175
|
+
title: '',
|
|
176
|
+
width: 32,
|
|
177
|
+
align: 'center',
|
|
178
|
+
render: (_, row) => (
|
|
179
|
+
<Popconfirm title={`${e('close')} pid: ${row.pid}?`} onConfirm={() => onKillProcess(row.pid)}>
|
|
180
|
+
<Button size='small' type='text' icon={<CloseCircleOutlined />} aria-label={`${e('close')} ${row.pid}`} />
|
|
181
|
+
</Popconfirm>
|
|
182
|
+
)
|
|
183
|
+
}]
|
|
184
|
+
: []),
|
|
185
|
+
{ key: 'pid', title: 'PID', dataIndex: 'pid', width: 60, copyValue: row => row.pid },
|
|
186
|
+
...desktopOnly([{ key: 'user', title: e('users'), dataIndex: 'user', ellipsis: true, width: 64, copyValue: row => row.user }]),
|
|
187
|
+
{ key: 'cpu', title: 'CPU', width: 52, render: (_, row) => `${row.cpu}%`, copyValue: row => `${row.cpu}%` },
|
|
188
|
+
{ key: 'memory', title: e('memory'), width: 72, render: (_, row) => formatBytes(row.memBytes), copyValue: row => formatBytes(row.memBytes) },
|
|
189
|
+
{ key: 'process', title: e('process'), dataIndex: 'cmd', ellipsis: true, copyValue: row => row.cmd }
|
|
190
|
+
]}
|
|
191
|
+
rowKey={row => row.pid}
|
|
192
|
+
rows={rows}
|
|
193
|
+
/>
|
|
194
|
+
)
|
|
195
|
+
: null
|
|
196
|
+
}
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function CpuDetail ({ snapshot, includeActivity, level }) {
|
|
202
|
+
const group = groupOf(snapshot, 'cpu')
|
|
203
|
+
const values = snapshot.cpuHistory.map(sample => sample.value)
|
|
204
|
+
const average = values.length
|
|
205
|
+
? values.reduce((sum, value) => sum + value, 0) / values.length
|
|
206
|
+
: null
|
|
207
|
+
return (
|
|
208
|
+
<>
|
|
209
|
+
<GroupState group={group} />
|
|
210
|
+
{
|
|
211
|
+
Number.isFinite(group.data)
|
|
212
|
+
? (
|
|
213
|
+
<>
|
|
214
|
+
<DetailRows rows={[
|
|
215
|
+
[e('current'), `${group.data.toFixed(1)}%`],
|
|
216
|
+
[e('average'), average === null ? null : `${average.toFixed(1)}%`],
|
|
217
|
+
[e('minimum'), values.length ? `${Math.min(...values).toFixed(1)}%` : null],
|
|
218
|
+
[e('maximum'), values.length ? `${Math.max(...values).toFixed(1)}%` : null]
|
|
219
|
+
]}
|
|
220
|
+
/>
|
|
221
|
+
<div className='remote-monitor-detail-chart'>
|
|
222
|
+
<Sparkline history={snapshot.cpuHistory} large level={level} />
|
|
223
|
+
</div>
|
|
224
|
+
</>
|
|
225
|
+
)
|
|
226
|
+
: null
|
|
227
|
+
}
|
|
228
|
+
{includeActivity ? <ActivityDetails group={groupOf(snapshot, 'activities')} sortBy='cpu' /> : null}
|
|
229
|
+
</>
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function MemoryDetail ({ snapshot, includeActivity, swapOnly }) {
|
|
234
|
+
const group = groupOf(snapshot, 'memory')
|
|
235
|
+
const memory = group.data
|
|
236
|
+
return (
|
|
237
|
+
<>
|
|
238
|
+
<GroupState group={group} />
|
|
239
|
+
{
|
|
240
|
+
memory
|
|
241
|
+
? (
|
|
242
|
+
<DetailRows rows={[
|
|
243
|
+
[e('used'), swapOnly ? null : `${formatBytes(memory.usedBytes)} (${memory.percent.toFixed(1)}%)`],
|
|
244
|
+
[e('available'), swapOnly ? null : formatBytes(memory.availableBytes)],
|
|
245
|
+
[e('total'), swapOnly ? null : formatBytes(memory.totalBytes)],
|
|
246
|
+
[e('swap'), memory.swapTotalBytes === null ? null : `${formatBytes(memory.swapUsedBytes)} / ${formatBytes(memory.swapTotalBytes)}`],
|
|
247
|
+
[e('compatibilityMemory'), !swapOnly && memory.compatibilityMode ? e('enabled') : null]
|
|
248
|
+
]}
|
|
249
|
+
/>
|
|
250
|
+
)
|
|
251
|
+
: null
|
|
252
|
+
}
|
|
253
|
+
{includeActivity && <ActivityDetails group={groupOf(snapshot, 'activities')} sortBy='memory' />}
|
|
254
|
+
</>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function NetworkDetail ({ snapshot, direction, hideIP }) {
|
|
259
|
+
const group = groupOf(snapshot, 'network')
|
|
260
|
+
const network = group.data
|
|
261
|
+
const primary = selectPrimaryNetwork(network)
|
|
262
|
+
const directions = direction === 'network' ? ['upload', 'download'] : [direction]
|
|
263
|
+
const columns = [
|
|
264
|
+
{
|
|
265
|
+
key: 'name',
|
|
266
|
+
title: e('interface'),
|
|
267
|
+
width: 84,
|
|
268
|
+
ellipsis: true,
|
|
269
|
+
render: (_, row) => row.name === primary?.name ? `${row.name} *` : row.name
|
|
270
|
+
},
|
|
271
|
+
...(hideIP
|
|
272
|
+
? []
|
|
273
|
+
: [{
|
|
274
|
+
key: 'address',
|
|
275
|
+
title: e('address'),
|
|
276
|
+
width: 96,
|
|
277
|
+
ellipsis: true,
|
|
278
|
+
render: (_, row) => row.ipv4 || '—'
|
|
279
|
+
}]),
|
|
280
|
+
...directions.flatMap(direction => {
|
|
281
|
+
const upload = direction === 'upload'
|
|
282
|
+
return [
|
|
283
|
+
{
|
|
284
|
+
key: direction,
|
|
285
|
+
title: e(direction),
|
|
286
|
+
width: 68,
|
|
287
|
+
render: (_, row) => formatRate(upload ? row.txRate : row.rxRate)
|
|
288
|
+
},
|
|
289
|
+
...desktopOnly([
|
|
290
|
+
{
|
|
291
|
+
key: `${direction}-total`,
|
|
292
|
+
title: upload ? e('sent') : e('received'),
|
|
293
|
+
width: 72,
|
|
294
|
+
render: (_, row) => formatBytes(upload ? row.txBytes : row.rxBytes)
|
|
295
|
+
}
|
|
296
|
+
])
|
|
297
|
+
]
|
|
298
|
+
})
|
|
299
|
+
]
|
|
300
|
+
return (
|
|
301
|
+
<>
|
|
302
|
+
<GroupState group={group} />
|
|
303
|
+
{
|
|
304
|
+
network
|
|
305
|
+
? (
|
|
306
|
+
<>
|
|
307
|
+
<DetailRows rows={[
|
|
308
|
+
[e('primaryInterface'), primary?.name]
|
|
309
|
+
]}
|
|
310
|
+
/>
|
|
311
|
+
<DetailTable
|
|
312
|
+
columns={columns}
|
|
313
|
+
rowKey={row => row.name}
|
|
314
|
+
rows={network.interfaces || []}
|
|
315
|
+
/>
|
|
316
|
+
</>
|
|
317
|
+
)
|
|
318
|
+
: null
|
|
319
|
+
}
|
|
320
|
+
</>
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function UsersDetail ({ snapshot, hideIP }) {
|
|
325
|
+
const group = groupOf(snapshot, 'users')
|
|
326
|
+
const users = group.data
|
|
327
|
+
return (
|
|
328
|
+
<>
|
|
329
|
+
<GroupState group={group} />
|
|
330
|
+
{
|
|
331
|
+
users
|
|
332
|
+
? users.sessions.length
|
|
333
|
+
? (
|
|
334
|
+
<DetailTable
|
|
335
|
+
columns={[
|
|
336
|
+
{ key: 'user', title: e('users'), dataIndex: 'user', ellipsis: true, width: 80 },
|
|
337
|
+
...desktopOnly([{ key: 'terminal', title: 'TTY', width: 64, ellipsis: true, render: (_, row) => row.terminal || '—' }]),
|
|
338
|
+
{ key: 'time', title: e('sessions'), width: 116, ellipsis: true, render: (_, row) => row.loginTime || '—' },
|
|
339
|
+
...(hideIP
|
|
340
|
+
? []
|
|
341
|
+
: desktopOnly([{ key: 'source', title: e('address'), width: 96, ellipsis: true, render: (_, row) => row.source || '—' }]))
|
|
342
|
+
]}
|
|
343
|
+
rows={users.sessions}
|
|
344
|
+
/>
|
|
345
|
+
)
|
|
346
|
+
: <div className='remote-monitor-detail-state'>{e('noUsers')}</div>
|
|
347
|
+
: null
|
|
348
|
+
}
|
|
349
|
+
</>
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function DisksDetail ({ snapshot, levels }) {
|
|
354
|
+
const group = groupOf(snapshot, 'disks')
|
|
355
|
+
const disks = sortDisks(group.data)
|
|
356
|
+
return (
|
|
357
|
+
<>
|
|
358
|
+
<GroupState group={group} />
|
|
359
|
+
{
|
|
360
|
+
disks.length
|
|
361
|
+
? (
|
|
362
|
+
<DetailTable
|
|
363
|
+
columns={[
|
|
364
|
+
{ key: 'mount', title: e('mount'), dataIndex: 'mount', ellipsis: true, width: 72 },
|
|
365
|
+
{ key: 'usage', title: e('used'), width: 60, render: (_, row) => <span className={`remote-monitor-level-${levels[row.mount] || 'unknown'}`}>{row.percent}%</span> },
|
|
366
|
+
{ key: 'used', title: e('used'), width: 72, ellipsis: true, render: (_, row) => formatBytes(row.usedBytes) },
|
|
367
|
+
...desktopOnly([{ key: 'available', title: e('available'), width: 76, ellipsis: true, render: (_, row) => formatBytes(row.availableBytes) }]),
|
|
368
|
+
{ key: 'total', title: e('total'), width: 72, ellipsis: true, render: (_, row) => formatBytes(row.totalBytes) },
|
|
369
|
+
...desktopOnly([{ key: 'filesystem', title: e('filesystem'), dataIndex: 'filesystem', ellipsis: true, width: 100 }])
|
|
370
|
+
]}
|
|
371
|
+
rowKey={row => `${row.filesystem}-${row.mount}`}
|
|
372
|
+
rows={disks}
|
|
373
|
+
/>
|
|
374
|
+
)
|
|
375
|
+
: null
|
|
376
|
+
}
|
|
377
|
+
</>
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// auto so a resize that flips store.isMobile re-renders the columns right away
|
|
382
|
+
export default auto(function MonitorDetails ({ id, snapshot, tab, config, levels, onClose, includeActivity = true, onKillProcess }) {
|
|
383
|
+
const sysInfo = groupOf(snapshot, 'sysinfo')
|
|
384
|
+
let body
|
|
385
|
+
if (id === 'hostname') {
|
|
386
|
+
body = (
|
|
387
|
+
<>
|
|
388
|
+
<GroupState group={sysInfo} />
|
|
389
|
+
<DetailRows rows={[
|
|
390
|
+
[e('hostname'), sysInfo.data?.hostname],
|
|
391
|
+
[e('address'), config.hideIP ? null : tab.host],
|
|
392
|
+
['OS', sysInfo.data?.os],
|
|
393
|
+
['Kernel', sysInfo.data?.kernel],
|
|
394
|
+
['Arch', sysInfo.data?.arch],
|
|
395
|
+
['Shell', [sysInfo.data?.shell, sysInfo.data?.shellVersion].filter(Boolean).join(' ')]
|
|
396
|
+
]}
|
|
397
|
+
/>
|
|
398
|
+
</>
|
|
399
|
+
)
|
|
400
|
+
} else if (id === 'cpu') {
|
|
401
|
+
body = <CpuDetail includeActivity={includeActivity} level={levels.cpu} snapshot={snapshot} />
|
|
402
|
+
} else if (id === 'cpuHistory') {
|
|
403
|
+
body = <CpuDetail level={levels.cpu} snapshot={snapshot} />
|
|
404
|
+
} else if (id === 'memory' || id === 'swap') {
|
|
405
|
+
body = <MemoryDetail snapshot={snapshot} includeActivity={includeActivity} swapOnly={id === 'swap'} />
|
|
406
|
+
} else if (id === 'upload' || id === 'download' || id === 'network') {
|
|
407
|
+
body = <NetworkDetail direction={id} hideIP={config.hideIP} snapshot={snapshot} />
|
|
408
|
+
} else if (id === 'uptime') {
|
|
409
|
+
const group = groupOf(snapshot, 'uptime')
|
|
410
|
+
body = (
|
|
411
|
+
<>
|
|
412
|
+
<GroupState group={group} />
|
|
413
|
+
{group.data
|
|
414
|
+
? <DetailRows rows={[
|
|
415
|
+
[e('uptime'), formatDuration(group.data.seconds)],
|
|
416
|
+
[e('bootTime'), group.data.bootTime ? new Date(group.data.bootTime).toLocaleString() : null]
|
|
417
|
+
]}
|
|
418
|
+
/>
|
|
419
|
+
: null}
|
|
420
|
+
</>
|
|
421
|
+
)
|
|
422
|
+
} else if (id === 'users') {
|
|
423
|
+
body = <UsersDetail snapshot={snapshot} hideIP={config.hideIP} />
|
|
424
|
+
} else if (id === 'activities') {
|
|
425
|
+
body = <ActivityDetails group={groupOf(snapshot, 'activities')} onKillProcess={onKillProcess} />
|
|
426
|
+
} else if (id === 'disks') {
|
|
427
|
+
body = <DisksDetail levels={levels.disks} snapshot={snapshot} />
|
|
428
|
+
}
|
|
429
|
+
return (
|
|
430
|
+
<div
|
|
431
|
+
className={'remote-monitor-details' + (onClose ? ' remote-monitor-popover' : '')}
|
|
432
|
+
data-monitor-detail={id}
|
|
433
|
+
onKeyDown={event => {
|
|
434
|
+
if (onClose && event.key === 'Escape') {
|
|
435
|
+
event.stopPropagation()
|
|
436
|
+
onClose()
|
|
437
|
+
}
|
|
438
|
+
}}
|
|
439
|
+
>
|
|
440
|
+
<div className='remote-monitor-detail-heading'>{e(id)}</div>
|
|
441
|
+
{body}
|
|
442
|
+
{onClose && (
|
|
443
|
+
<Button
|
|
444
|
+
icon={<BarChartOutlined />}
|
|
445
|
+
onClick={() => {
|
|
446
|
+
onClose()
|
|
447
|
+
window.store.openInfoPanel()
|
|
448
|
+
}}
|
|
449
|
+
onMouseDown={event => event.preventDefault()}
|
|
450
|
+
size='small'
|
|
451
|
+
type='link'
|
|
452
|
+
>
|
|
453
|
+
{e('info')}
|
|
454
|
+
</Button>
|
|
455
|
+
)}
|
|
456
|
+
</div>
|
|
457
|
+
)
|
|
458
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
.remote-monitor-sparkline
|
|
2
|
+
display block
|
|
3
|
+
overflow visible
|
|
4
|
+
color var(--primary)
|
|
5
|
+
transition color 120ms ease
|
|
6
|
+
|
|
7
|
+
.remote-monitor-empty
|
|
8
|
+
color var(--text-disabled)
|
|
9
|
+
|
|
10
|
+
.remote-monitor-popover
|
|
11
|
+
// 488 - antd popover innerPadding 12*2 = 464 content, just clears the widest table
|
|
12
|
+
// (network, Σ列宽 460) so the desktop popover never needs a horizontal scrollbar
|
|
13
|
+
width 488px
|
|
14
|
+
max-width calc(100vw - 32px)
|
|
15
|
+
max-height calc(100dvh - 80px - var(--shortcut-bar-kb-offset, 0px))
|
|
16
|
+
// tables own their own horizontal overflow via scroll.x, so the panel never needs
|
|
17
|
+
// to scroll sideways (that would drag the heading and the info button along)
|
|
18
|
+
overflow-x hidden
|
|
19
|
+
overflow-y auto
|
|
20
|
+
|
|
21
|
+
.remote-monitor-detail-heading
|
|
22
|
+
margin-bottom 8px
|
|
23
|
+
color var(--text)
|
|
24
|
+
font-size 13px
|
|
25
|
+
font-weight 600
|
|
26
|
+
|
|
27
|
+
.remote-monitor-detail-state
|
|
28
|
+
margin 6px 0
|
|
29
|
+
color var(--text-disabled)
|
|
30
|
+
|
|
31
|
+
.remote-monitor-detail-rows
|
|
32
|
+
margin 0 0 8px
|
|
33
|
+
|
|
34
|
+
.remote-monitor-detail-row
|
|
35
|
+
display grid
|
|
36
|
+
grid-template-columns minmax(0, 0.45fr) minmax(0, 1fr)
|
|
37
|
+
gap 10px
|
|
38
|
+
padding 3px 0
|
|
39
|
+
dt
|
|
40
|
+
color var(--text-dark)
|
|
41
|
+
dd
|
|
42
|
+
min-width 0
|
|
43
|
+
margin 0
|
|
44
|
+
overflow hidden
|
|
45
|
+
text-overflow ellipsis
|
|
46
|
+
color var(--text)
|
|
47
|
+
font-variant-numeric tabular-nums
|
|
48
|
+
|
|
49
|
+
.remote-monitor-detail-chart
|
|
50
|
+
display flex
|
|
51
|
+
align-items center
|
|
52
|
+
justify-content center
|
|
53
|
+
margin 8px 0
|
|
54
|
+
padding 4px
|
|
55
|
+
.remote-monitor-sparkline
|
|
56
|
+
max-width 100%
|
|
57
|
+
|
|
58
|
+
.remote-monitor-activity
|
|
59
|
+
margin-top 10px
|
|
60
|
+
|
|
61
|
+
// antd Table owns the whole table layout, only the dense metrics look is kept
|
|
62
|
+
.remote-monitor-table
|
|
63
|
+
font-variant-numeric tabular-nums
|
|
64
|
+
.ant-table.ant-table-small
|
|
65
|
+
font-size 11px
|
|
66
|
+
|
|
67
|
+
// click a value cell copies it (see withCellCopy)
|
|
68
|
+
.remote-monitor-copy-cell
|
|
69
|
+
cursor pointer
|
|
70
|
+
&:hover
|
|
71
|
+
color var(--primary)
|
|
72
|
+
|
|
73
|
+
.remote-monitor-item.remote-monitor-level-warning,
|
|
74
|
+
.remote-monitor-level-warning
|
|
75
|
+
color var(--warn)
|
|
76
|
+
|
|
77
|
+
.remote-monitor-item.remote-monitor-level-critical,
|
|
78
|
+
.remote-monitor-level-critical
|
|
79
|
+
color var(--error)
|
|
80
|
+
|
|
81
|
+
.remote-monitor-item.remote-monitor-level-unknown,
|
|
82
|
+
.remote-monitor-level-unknown
|
|
83
|
+
color var(--text-disabled)
|
|
84
|
+
|
|
85
|
+
@media (prefers-reduced-motion: reduce)
|
|
86
|
+
.remote-monitor-sparkline
|
|
87
|
+
transition none
|