@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.
Files changed (149) hide show
  1. package/README.md +246 -12
  2. package/client/common/choose-save-folder.js +21 -2
  3. package/client/common/constants.js +23 -6
  4. package/client/common/create-title.jsx +6 -0
  5. package/client/common/default-setting.js +14 -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/bookmark-form/index.jsx +10 -8
  29. package/client/components/common/item-filter.jsx +76 -0
  30. package/client/components/common/item-filter.styl +54 -0
  31. package/client/components/common/lazy-boundary.jsx +34 -0
  32. package/client/components/common/opacity.jsx +3 -1
  33. package/client/components/footer/footer-entry.jsx +35 -5
  34. package/client/components/footer/footer.styl +8 -1
  35. package/client/components/layout/layout.jsx +23 -31
  36. package/client/components/layout/pixed.js +3 -0
  37. package/client/components/main/main.jsx +80 -28
  38. package/client/components/main/term-fullscreen.styl +9 -3
  39. package/client/components/quick-commands/qm.styl +3 -2
  40. package/client/components/rdp/file-transfer.js +4 -4
  41. package/client/components/remote-monitor/monitor-details.jsx +458 -0
  42. package/client/components/remote-monitor/monitor-details.styl +87 -0
  43. package/client/components/remote-monitor/monitor-model.js +764 -0
  44. package/client/components/remote-monitor/remote-monitor-bar-entry.jsx +18 -0
  45. package/client/components/remote-monitor/remote-monitor-bar.jsx +344 -0
  46. package/client/components/remote-monitor/remote-monitor-bar.styl +106 -0
  47. package/client/components/remote-monitor/session-monitor.js +361 -0
  48. package/client/components/remote-monitor/use-monitor-details.js +10 -0
  49. package/client/components/remote-monitor/visibility.js +18 -0
  50. package/client/components/session/session-control.jsx +29 -2
  51. package/client/components/session/session.jsx +40 -18
  52. package/client/components/session/sessions.jsx +5 -2
  53. package/client/components/setting-panel/col.jsx +32 -6
  54. package/client/components/setting-panel/list.styl +12 -0
  55. package/client/components/setting-panel/setting-common.jsx +25 -56
  56. package/client/components/setting-panel/setting-mobile-breadcrumb.jsx +29 -0
  57. package/client/components/setting-panel/setting-modal.jsx +64 -44
  58. package/client/components/setting-panel/setting-terminal.jsx +7 -2
  59. package/client/components/setting-panel/setting-wrap.jsx +0 -3
  60. package/client/components/setting-panel/setting-wrap.styl +80 -11
  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/side-panel-r/side-panel-r.jsx +2 -2
  67. package/client/components/sidebar/index.jsx +28 -21
  68. package/client/components/sidebar/left-sidebar-icons.jsx +1 -1
  69. package/client/components/sidebar/sidebar.styl +16 -0
  70. package/client/components/sys-menu/menu-btn.jsx +30 -1
  71. package/client/components/tabs/index.jsx +2 -2
  72. package/client/components/tabs/tab-title.jsx +3 -2
  73. package/client/components/tabs/tab.jsx +5 -1
  74. package/client/components/tabs/tabs.styl +4 -0
  75. package/client/components/terminal/attach-addon-custom.js +295 -49
  76. package/client/components/terminal/automation/index.js +64 -0
  77. package/client/components/terminal/automation/keys.js +49 -0
  78. package/client/components/terminal/automation/stream-matcher.js +82 -0
  79. package/client/components/terminal/automation/strip-ansi.js +19 -0
  80. package/client/components/terminal/automation/trigger-engine.js +233 -0
  81. package/client/components/terminal/automation/trigger-presets.js +110 -0
  82. package/client/components/terminal/command-tracker-addon.js +31 -1
  83. package/client/components/terminal/mixins/index.js +27 -0
  84. package/client/components/terminal/mixins/term-attach.js +123 -0
  85. package/client/components/terminal/mixins/term-config.jsx +84 -0
  86. package/client/components/terminal/mixins/term-content.js +88 -0
  87. package/client/components/terminal/mixins/term-context-menu.jsx +291 -0
  88. package/client/components/terminal/mixins/term-file-drop.js +149 -0
  89. package/client/components/terminal/mixins/term-init.js +122 -0
  90. package/client/components/terminal/mixins/term-log.jsx +96 -0
  91. package/client/components/terminal/mixins/term-resize.js +87 -0
  92. package/client/components/terminal/mixins/term-socket.js +433 -0
  93. package/client/components/terminal/mixins/term-suggestions.js +224 -0
  94. package/client/components/terminal/mixins/term-theme.js +190 -0
  95. package/client/components/terminal/mixins/term-touch.js +136 -0
  96. package/client/components/terminal/shell-detect.js +37 -0
  97. package/client/components/terminal/shell.js +0 -28
  98. package/client/components/terminal/shortcut-bar-entry.jsx +32 -27
  99. package/client/components/terminal/shortcut-bar.jsx +4 -6
  100. package/client/components/terminal/shortcut-bar.styl +4 -0
  101. package/client/components/terminal/ssh-reload-state.js +56 -0
  102. package/client/components/terminal/startup-queue.js +379 -0
  103. package/client/components/terminal/terminal-interactive.jsx +32 -0
  104. package/client/components/terminal/terminal.jsx +102 -2007
  105. package/client/components/terminal/transfer-client-base.js +5 -29
  106. package/client/components/terminal/trzsz-client.js +1 -3
  107. package/client/components/terminal/xmodem-client.js +1 -3
  108. package/client/components/terminal/xterm-loader.js +7 -0
  109. package/client/components/terminal/zmodem-client.js +1 -3
  110. package/client/components/terminal-info/base.jsx +18 -70
  111. package/client/components/terminal-info/terminal-info-entry.jsx +13 -5
  112. package/client/components/terminal-info/terminal-info.jsx +50 -59
  113. package/client/components/terminal-info/terminal-info.styl +6 -49
  114. package/client/components/text-editor/open-text-editor.js +30 -0
  115. package/client/components/text-editor/text-editor-entry.jsx +17 -6
  116. package/client/components/text-editor/text-editor.jsx +14 -0
  117. package/client/components/theme/theme-form.jsx +22 -0
  118. package/client/components/tree-list/bookmark-transport.jsx +7 -31
  119. package/client/components/tree-list/tree-list.styl +13 -0
  120. package/client/components/triggers/trigger-editor.jsx +246 -0
  121. package/client/components/triggers/trigger-form.jsx +200 -0
  122. package/client/components/triggers/trigger-session-modal.jsx +199 -0
  123. package/client/components/triggers/trigger-setting-list.jsx +89 -0
  124. package/client/components/triggers/trigger-transport.jsx +60 -0
  125. package/client/components/triggers/triggers-field.jsx +19 -0
  126. package/client/components/widgets/widgets-list.jsx +11 -1
  127. package/client/css/mobile.styl +9 -1
  128. package/client/store/common.js +12 -1
  129. package/client/store/init-state.js +14 -0
  130. package/client/store/mcp-handler.js +158 -30
  131. package/client/store/setting.js +20 -0
  132. package/client/store/sidebar.js +12 -0
  133. package/client/store/store.js +2 -0
  134. package/client/store/sync.js +45 -9
  135. package/client/store/system-menu.js +2 -0
  136. package/client/store/tab.js +41 -3
  137. package/client/store/trigger.js +118 -0
  138. package/client/store/watch.js +13 -5
  139. package/client/views/index.pug +3 -0
  140. package/package.json +1 -1
  141. package/client/components/terminal-info/activity.jsx +0 -57
  142. package/client/components/terminal-info/data-cols-parser.jsx +0 -54
  143. package/client/components/terminal-info/disk.jsx +0 -45
  144. package/client/components/terminal-info/format-disks.js +0 -45
  145. package/client/components/terminal-info/network.jsx +0 -121
  146. package/client/components/terminal-info/resource.jsx +0 -99
  147. package/client/components/terminal-info/run-cmd.jsx +0 -263
  148. package/client/components/terminal-info/sys-info.jsx +0 -56
  149. package/client/components/terminal-info/up.jsx +0 -17
@@ -0,0 +1,18 @@
1
+ import { lazy, Suspense } from 'react'
2
+ import { auto } from 'manate/react'
3
+ import { isRemoteMonitorBarVisible } from './visibility'
4
+ import LazyBoundary from '../common/lazy-boundary'
5
+
6
+ const RemoteMonitorBar = lazy(() => import('./remote-monitor-bar'))
7
+
8
+ export default auto(function RemoteMonitorBarEntry (props) {
9
+ if (!isRemoteMonitorBarVisible(props.store)) return null
10
+
11
+ return (
12
+ <LazyBoundary>
13
+ <Suspense fallback={null}>
14
+ <RemoteMonitorBar {...props} />
15
+ </Suspense>
16
+ </LazyBoundary>
17
+ )
18
+ })
@@ -0,0 +1,344 @@
1
+ import { auto } from 'manate/react'
2
+ import { Button, Popover } from 'antd'
3
+ import {
4
+ CloseOutlined,
5
+ WarningOutlined
6
+ } from '@ant-design/icons'
7
+ import { useEffect, useMemo, useState } from 'react'
8
+ import { statusMap } from '../../common/constants'
9
+ import {
10
+ formatBytes,
11
+ groupOf,
12
+ sortDisks,
13
+ MONITOR_ITEM_GROUP,
14
+ formatRate,
15
+ normalizeRemoteMonitorItems,
16
+ REMOTE_MONITOR_ITEM_IDS,
17
+ selectPrimaryNetwork
18
+ } from './monitor-model'
19
+ import { useMonitorDetails } from './use-monitor-details'
20
+ import MonitorDetails, { Sparkline } from './monitor-details'
21
+ import { getRemoteMonitorTab, isRemoteMonitorBarVisible } from './visibility'
22
+ import { runCmd } from '../terminal/terminal-apis'
23
+ import ItemFilter from '../common/item-filter'
24
+ import './remote-monitor-bar.styl'
25
+
26
+ const e = window.translate
27
+
28
+ function severity (level) {
29
+ return {
30
+ unknown: -1,
31
+ normal: 0,
32
+ warning: 1,
33
+ critical: 2
34
+ }[level] ?? -1
35
+ }
36
+
37
+ function combineLevel (levels) {
38
+ return levels.reduce((current, level) => {
39
+ return severity(level) > severity(current) ? level : current
40
+ }, 'unknown')
41
+ }
42
+
43
+ function statusMarker (level) {
44
+ if (level !== 'warning' && level !== 'critical') {
45
+ return null
46
+ }
47
+ return (
48
+ <WarningOutlined
49
+ aria-hidden='true'
50
+ className={`remote-monitor-status-marker remote-monitor-level-${level}`}
51
+ />
52
+ )
53
+ }
54
+
55
+ function statusText (level) {
56
+ if (level === 'critical') return e('critical')
57
+ if (level === 'warning') return e('warning')
58
+ if (level === 'unknown') return e('unknown')
59
+ return e('normal')
60
+ }
61
+
62
+ function compactDuration (seconds) {
63
+ const value = Math.max(0, Math.floor(seconds))
64
+ const days = Math.floor(value / 86400)
65
+ const hours = Math.floor((value % 86400) / 3600)
66
+ const minutes = Math.floor((value % 3600) / 60)
67
+ const secs = value % 60
68
+ if (days) return `${days}d ${String(hours).padStart(2, '0')}h`
69
+ if (hours) return `${hours}h ${String(minutes).padStart(2, '0')}m`
70
+ if (minutes) return `${minutes}m ${String(secs).padStart(2, '0')}s`
71
+ return `${secs}s`
72
+ }
73
+
74
+ function summaryFor (id, snapshot, levels) {
75
+ if (id === 'hostname') {
76
+ return groupOf(snapshot, 'sysinfo').data?.hostname || '—'
77
+ }
78
+ if (id === 'cpu') {
79
+ const value = groupOf(snapshot, 'cpu').data
80
+ return `CPU ${Number.isFinite(value) ? `${Math.round(value)}%` : '—'}`
81
+ }
82
+ if (id === 'cpuHistory') {
83
+ return <Sparkline history={snapshot.cpuHistory} level={levels.cpu} />
84
+ }
85
+ if (id === 'memory') {
86
+ const memory = groupOf(snapshot, 'memory').data
87
+ return memory
88
+ ? `Mem ${formatBytes(memory.usedBytes)} / ${formatBytes(memory.totalBytes)}`
89
+ : 'Mem —'
90
+ }
91
+ if (id === 'upload' || id === 'download') {
92
+ const network = groupOf(snapshot, 'network').data
93
+ const primary = selectPrimaryNetwork(network)
94
+ const rate = id === 'upload' ? primary?.txRate : primary?.rxRate
95
+ return `${id === 'upload' ? '↑' : '↓'} ${formatRate(rate)}`
96
+ }
97
+ if (id === 'uptime') {
98
+ const uptime = groupOf(snapshot, 'uptime').data
99
+ return `Up ${uptime ? compactDuration(uptime.seconds) : '—'}`
100
+ }
101
+ if (id === 'users') {
102
+ const users = groupOf(snapshot, 'users').data?.users
103
+ if (!users) return 'Users —'
104
+ if (!users.length) return 'Users 0'
105
+ return users.length > 1 ? `${users[0]} +${users.length - 1}` : users[0]
106
+ }
107
+ if (id === 'disks') {
108
+ const disks = sortDisks(groupOf(snapshot, 'disks').data)
109
+ if (!disks.length) return 'Disk —'
110
+ const shown = disks.slice(0, 3)
111
+ const groupReady = groupOf(snapshot, 'disks').status === 'ready'
112
+ return (
113
+ <span className='remote-monitor-disk-summary'>
114
+ {
115
+ shown.map(disk => {
116
+ const level = groupReady
117
+ ? levels.disks[disk.mount] || 'unknown'
118
+ : 'unknown'
119
+ return (
120
+ <span
121
+ className={`remote-monitor-disk-value remote-monitor-level-${level}`}
122
+ key={disk.mount}
123
+ >
124
+ {disk.mount}:{disk.percent}%
125
+ </span>
126
+ )
127
+ })
128
+ }
129
+ {disks.length > shown.length ? <span>+{disks.length - shown.length}</span> : null}
130
+ </span>
131
+ )
132
+ }
133
+ return '—'
134
+ }
135
+
136
+ function accessibleSummaryFor (id, summary, snapshot) {
137
+ if (typeof summary === 'string' || typeof summary === 'number') {
138
+ return String(summary)
139
+ }
140
+ if (id === 'cpuHistory') {
141
+ const latest = snapshot.cpuHistory[snapshot.cpuHistory.length - 1]?.value
142
+ return Number.isFinite(latest) ? `${Math.round(latest)}%` : '—'
143
+ }
144
+ if (id === 'disks') {
145
+ const disks = sortDisks(groupOf(snapshot, 'disks').data)
146
+ if (!disks.length) return '—'
147
+ const shown = disks.slice(0, 3).map(disk => `${disk.mount}: ${disk.percent}%`)
148
+ return `${shown.join(', ')}${disks.length > shown.length ? `, +${disks.length - shown.length}` : ''}`
149
+ }
150
+ return '—'
151
+ }
152
+
153
+ function levelForItem (id, levels, snapshot) {
154
+ const group = groupOf(snapshot, MONITOR_ITEM_GROUP[id])
155
+ if (group.status !== 'ready') {
156
+ return 'unknown'
157
+ }
158
+ if (id === 'cpu' || id === 'cpuHistory') return levels.cpu
159
+ if (id === 'memory') return levels.memory
160
+ if (id === 'disks') return combineLevel(Object.values(levels.disks))
161
+ if (id === 'upload' || id === 'download') {
162
+ const primary = selectPrimaryNetwork(group.data)
163
+ const rate = id === 'upload' ? primary?.txRate : primary?.rxRate
164
+ return Number.isFinite(rate) ? 'normal' : 'unknown'
165
+ }
166
+ return group.data === null || group.data === undefined ? 'unknown' : 'normal'
167
+ }
168
+
169
+ export default auto(function RemoteMonitorBar ({ store, style }) {
170
+ const visible = isRemoteMonitorBarVisible(store)
171
+ const config = store.config
172
+ const tab = getRemoteMonitorTab(store) || {}
173
+ const itemsKey = JSON.stringify(config.remoteMonitorBarItems)
174
+ const items = useMemo(
175
+ () => normalizeRemoteMonitorItems(config.remoteMonitorBarItems),
176
+ [itemsKey]
177
+ )
178
+ const enabledItems = items.filter(item => item.enabled)
179
+ const [openId, setOpenId] = useState(null)
180
+ const [pinnedId, setPinnedId] = useState(null)
181
+ const [filterOpen, setFilterOpen] = useState(false)
182
+ // Touch has no hover, so the details popover there is click-driven: a tap
183
+ // toggles it and a tap outside closes it (store.isTouchDevice follows the
184
+ // input the user actually uses, see main.jsx). The hover/pin bookkeeping
185
+ // below is mouse-only.
186
+ const touchMode = !!store.isTouchDevice
187
+ const connected = visible && tab.status === statusMap.success
188
+ const requestedItems = enabledItems.map(item => item.id)
189
+ if (openId === 'cpu' || openId === 'memory') requestedItems.push('activities')
190
+ const { snapshot, levels } = useMonitorDetails(tab.id || '', requestedItems, connected)
191
+
192
+ useEffect(() => {
193
+ setOpenId(null)
194
+ setPinnedId(null)
195
+ }, [tab.id, itemsKey, visible])
196
+
197
+ // keep the controls revealed while the filter popover is open, the popover
198
+ // lives in a portal so hovering it does not keep the bar hovered
199
+ useEffect(() => {
200
+ setFilterOpen(false)
201
+ }, [tab.id, visible])
202
+
203
+ if (!visible) {
204
+ return null
205
+ }
206
+
207
+ function handleOpenChange (id, open) {
208
+ if (touchMode) {
209
+ // the popover click trigger owns open/close
210
+ if (open) {
211
+ setOpenId(id)
212
+ } else {
213
+ closePopover()
214
+ }
215
+ return
216
+ }
217
+ if (open) {
218
+ setOpenId(id)
219
+ if (pinnedId && pinnedId !== id) {
220
+ setPinnedId(null)
221
+ }
222
+ } else if (pinnedId !== id) {
223
+ setOpenId(current => current === id ? null : current)
224
+ }
225
+ }
226
+
227
+ function handleClick (id) {
228
+ if (touchMode) {
229
+ // click trigger already toggled it, don't toggle twice
230
+ return
231
+ }
232
+ if (pinnedId === id) {
233
+ setPinnedId(null)
234
+ setOpenId(null)
235
+ } else {
236
+ setPinnedId(id)
237
+ setOpenId(id)
238
+ }
239
+ }
240
+
241
+ function closePopover () {
242
+ setPinnedId(null)
243
+ setOpenId(null)
244
+ }
245
+
246
+ function toggleItem (id) {
247
+ const next = items.map(item => (
248
+ item.id === id ? { ...item, enabled: !item.enabled } : item
249
+ ))
250
+ store.setConfig({
251
+ // keep the stored shape in sync with the setting panel: enabled ids only
252
+ remoteMonitorBarItems: next.filter(item => item.enabled).map(item => item.id)
253
+ })
254
+ }
255
+
256
+ const connectionMessage = tab.status === statusMap.error
257
+ ? e('disconnected')
258
+ : e('loading')
259
+
260
+ return (
261
+ <div
262
+ aria-label={e('remoteMonitorBar')}
263
+ className={'remote-monitor-bar' + (filterOpen ? ' remote-monitor-bar-filter-open' : '')}
264
+ onKeyDown={event => {
265
+ if (event.key === 'Escape') {
266
+ closePopover()
267
+ }
268
+ }}
269
+ role='region'
270
+ style={style}
271
+ >
272
+ <div className='remote-monitor-scroll'>
273
+ {
274
+ connected
275
+ ? enabledItems.length
276
+ ? enabledItems.map(item => {
277
+ const level = levelForItem(item.id, levels, snapshot)
278
+ const classLevel = item.id === 'disks' && level !== 'unknown'
279
+ ? 'normal'
280
+ : level
281
+ const label = e(item.id)
282
+ const summary = summaryFor(item.id, snapshot, levels)
283
+ const accessibleSummary = accessibleSummaryFor(item.id, summary, snapshot)
284
+ return (
285
+ <Popover
286
+ autoAdjustOverflow
287
+ content={(
288
+ <MonitorDetails
289
+ config={config}
290
+ id={item.id}
291
+ levels={levels}
292
+ onClose={closePopover}
293
+ onKillProcess={pid => runCmd(tab.id, `kill ${pid}`)}
294
+ snapshot={snapshot}
295
+ tab={tab}
296
+ />
297
+ )}
298
+ key={item.id}
299
+ mouseEnterDelay={0.2}
300
+ mouseLeaveDelay={0.15}
301
+ onOpenChange={open => handleOpenChange(item.id, open)}
302
+ open={openId === item.id}
303
+ placement='top'
304
+ trigger={touchMode ? 'click' : ['hover', 'focus']}
305
+ >
306
+ <button
307
+ aria-label={`${label}: ${accessibleSummary}; ${statusText(level)}`}
308
+ className={`remote-monitor-item remote-monitor-item-${item.id} remote-monitor-level-${classLevel}`}
309
+ data-monitor-item={item.id}
310
+ onClick={() => handleClick(item.id)}
311
+ title={label}
312
+ type='button'
313
+ >
314
+ {statusMarker(level)}
315
+ {summary}
316
+ </button>
317
+ </Popover>
318
+ )
319
+ })
320
+ : <div className='remote-monitor-message'>{e('noItems')}</div>
321
+ : <div className='remote-monitor-message'>{connectionMessage}</div>
322
+ }
323
+ </div>
324
+ <div className='remote-monitor-controls'>
325
+ <ItemFilter
326
+ className='remote-monitor-filter'
327
+ ids={REMOTE_MONITOR_ITEM_IDS}
328
+ onOpenChange={setFilterOpen}
329
+ onToggle={toggleItem}
330
+ placement='topRight'
331
+ selected={enabledItems.map(item => item.id)}
332
+ />
333
+ <Button
334
+ aria-label={e('close')}
335
+ icon={<CloseOutlined />}
336
+ onClick={() => store.setConfig({ remoteMonitorBarEnabled: false })}
337
+ size='small'
338
+ title={e('close')}
339
+ type='text'
340
+ />
341
+ </div>
342
+ </div>
343
+ )
344
+ })
@@ -0,0 +1,106 @@
1
+ .remote-monitor-bar
2
+ position absolute
3
+ bottom 36px
4
+ z-index 188
5
+ overflow hidden
6
+ display flex
7
+ background var(--main)
8
+ color var(--text)
9
+ font-size 12px
10
+ font-variant-numeric tabular-nums
11
+ user-select none
12
+
13
+ .remote-monitor-scroll
14
+ min-width 0
15
+ flex 1
16
+ display flex
17
+ overflow-x auto
18
+ overflow-y hidden
19
+ scrollbar-width none // Firefox
20
+ -ms-overflow-style none // IE/Edge
21
+ overscroll-behavior-x contain
22
+ &::-webkit-scrollbar
23
+ width 0
24
+ height 0
25
+ display none
26
+
27
+ .remote-monitor-item
28
+ height 100%
29
+ flex-shrink 0
30
+ display inline-flex
31
+ align-items center
32
+ gap 4px
33
+ padding 0 9px
34
+ border 0
35
+ outline none
36
+ background transparent
37
+ color var(--text)
38
+ font inherit
39
+ white-space nowrap
40
+ cursor default
41
+ transition color 120ms ease, background-color 120ms ease
42
+ &:hover,
43
+ &:focus-visible
44
+ background var(--main-light)
45
+ &:focus-visible
46
+ box-shadow inset 0 0 0 1px var(--primary)
47
+
48
+ .remote-monitor-item-hostname
49
+ display block
50
+ max-width 220px
51
+ overflow hidden
52
+ text-overflow ellipsis
53
+
54
+ .remote-monitor-disk-summary
55
+ display inline-flex
56
+ align-items center
57
+ gap 6px
58
+
59
+ .remote-monitor-status-marker
60
+ flex-shrink 0
61
+ font-size 10px
62
+
63
+ .remote-monitor-message
64
+ display flex
65
+ align-items center
66
+ min-width max-content
67
+ padding 0 10px
68
+ color var(--text-disabled)
69
+ font-variant-numeric tabular-nums
70
+
71
+ .remote-monitor-controls
72
+ flex-shrink 0
73
+ display flex
74
+ align-items center
75
+ opacity 0
76
+ pointer-events none
77
+ .ant-btn
78
+ width 28px
79
+ height 100%
80
+ padding 0
81
+ color var(--text-dark)
82
+ &:hover,
83
+ &:focus-visible
84
+ color var(--text)
85
+ background var(--main-light)
86
+ .item-filter
87
+ height 100%
88
+ padding 0 6px
89
+ &:hover
90
+ background var(--main-light)
91
+ &:focus-visible
92
+ outline none
93
+ box-shadow inset 0 0 0 1px var(--primary)
94
+
95
+ .remote-monitor-bar:hover,
96
+ .remote-monitor-bar:focus-within,
97
+ .remote-monitor-bar-filter-open,
98
+ .is-mobile,
99
+ .is-touch-device
100
+ .remote-monitor-controls
101
+ opacity 1
102
+ pointer-events auto
103
+
104
+ @media (prefers-reduced-motion: reduce)
105
+ .remote-monitor-item
106
+ transition none