fix(ui): restore status bar metrics layout and simplify Dynamic list
Drop the tab-kind badge, keep connection/forward labels from wrapping, and adapt disk chips by spare width; Dynamic card is start/stop only with port edited in the form. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b0d0e3a5f3
commit
883b240f73
2 files changed
+50
-74
No files matched your search
@@ -209,10 +209,22 @@ function RemoteHostMetricsStrip({
|
||||
transferLabel?: string | null
|
||||
}) {
|
||||
const { t } = useTranslation('common')
|
||||
const stripRef = useRef<HTMLDivElement>(null)
|
||||
const [diskSlots, setDiskSlots] = useState(1)
|
||||
|
||||
const osLabel = (metrics?.osPrettyName || fallbackOs || '').trim() || null
|
||||
const tz = metrics ? timezoneLabel(metrics) : null
|
||||
const tzTip = metrics ? timezoneTooltip(metrics) : null
|
||||
const disks = (metrics?.disks ?? []).slice(0, 3)
|
||||
const rankedDisks = useMemo(
|
||||
() =>
|
||||
(metrics?.disks ?? [])
|
||||
.slice()
|
||||
.sort((a, b) => b.percent - a.percent || a.mount.localeCompare(b.mount))
|
||||
.slice(0, 5),
|
||||
[metrics?.disks]
|
||||
)
|
||||
const visibleDisks = rankedDisks.slice(0, diskSlots)
|
||||
|
||||
const memDetail =
|
||||
metrics?.memUsedKb != null && metrics?.memTotalKb != null
|
||||
? ` (${formatBytes(metrics.memUsedKb * 1024)}/${formatBytes(metrics.memTotalKb * 1024)})`
|
||||
@@ -223,30 +235,56 @@ function RemoteHostMetricsStrip({
|
||||
metrics?.cpuPercent != null ? `CPU ${formatCpuPercent(metrics.cpuPercent)}` : null,
|
||||
metrics?.memPercent != null ? t('metrics.memory', { percent: metrics.memPercent, detail: memDetail }) : null,
|
||||
transferLabel ? t('metrics.sessionTransfer', { value: transferLabel.replace(/^↑↓\s*/, '') }) : null,
|
||||
...disks.map(
|
||||
...rankedDisks.map(
|
||||
(d) =>
|
||||
`${d.mount} ${d.percent}% (${formatBytes(d.usedKb * 1024)}/${formatBytes(d.totalKb * 1024)})`
|
||||
),
|
||||
].filter(Boolean)
|
||||
|
||||
useEffect(() => {
|
||||
const el = stripRef.current
|
||||
const parent = el?.parentElement
|
||||
if (!el || !parent) return
|
||||
|
||||
const updateSlots = () => {
|
||||
const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]')
|
||||
const spare = spacer?.clientWidth ?? 0
|
||||
// ~70px per chip; fullscreen usually has room for top 5.
|
||||
if (spare >= 360) setDiskSlots(5)
|
||||
else if (spare >= 290) setDiskSlots(4)
|
||||
else if (spare >= 220) setDiskSlots(3)
|
||||
else if (spare >= 150) setDiskSlots(2)
|
||||
else if (spare >= 72) setDiskSlots(1)
|
||||
else setDiskSlots(0)
|
||||
}
|
||||
|
||||
updateSlots()
|
||||
const observer = new ResizeObserver(updateSlots)
|
||||
observer.observe(parent)
|
||||
const spacer = parent.querySelector<HTMLElement>('[data-statusbar-spacer]')
|
||||
if (spacer) observer.observe(spacer)
|
||||
return () => observer.disconnect()
|
||||
}, [rankedDisks.length, osLabel, tz, transferLabel, metrics?.cpuPercent, metrics?.memPercent])
|
||||
|
||||
const hasAny =
|
||||
osLabel ||
|
||||
tz ||
|
||||
metrics?.cpuPercent != null ||
|
||||
metrics?.memPercent != null ||
|
||||
transferLabel ||
|
||||
disks.length > 0
|
||||
rankedDisks.length > 0
|
||||
if (!hasAny) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ml-3 flex min-w-0 max-w-[58vw] items-center gap-2 overflow-hidden font-mono tabular-nums ${
|
||||
ref={stripRef}
|
||||
className={`ml-3 flex min-w-0 items-center gap-2 overflow-hidden whitespace-nowrap font-mono tabular-nums ${
|
||||
stale ? 'opacity-60' : 'opacity-100'
|
||||
}`}
|
||||
title={tipParts.join(' · ')}
|
||||
>
|
||||
{osLabel && (
|
||||
<span className="text-content-secondary max-w-[11rem] shrink-0 truncate" title={osLabel}>
|
||||
<span className="text-content-secondary max-w-[10rem] shrink truncate" title={osLabel}>
|
||||
{osLabel}
|
||||
</span>
|
||||
)}
|
||||
@@ -265,8 +303,8 @@ function RemoteHostMetricsStrip({
|
||||
{transferLabel}
|
||||
</span>
|
||||
)}
|
||||
{disks.map((d) => (
|
||||
<span key={d.mount} className={`hidden shrink-0 lg:inline ${pctClass(d.percent)}`}>
|
||||
{visibleDisks.map((d) => (
|
||||
<span key={d.mount} className={`shrink-0 ${pctClass(d.percent)}`}>
|
||||
{shortMount(d.mount)} {d.percent}%
|
||||
</span>
|
||||
))}
|
||||
@@ -296,7 +334,7 @@ export default function StatusBar() {
|
||||
|
||||
const activeTab = useMemo(() => tabs.find((t) => t.id === activeTabId), [tabs, activeTabId])
|
||||
|
||||
const { connectionState, hostLabel, stats, tabKind, connectionId, isRemoteSsh, sessionOsPretty } =
|
||||
const { connectionState, hostLabel, stats, connectionId, isRemoteSsh, sessionOsPretty } =
|
||||
useMemo(() => {
|
||||
if (!activeTab || activeTab.type === 'welcome' || activeTab.type === 'settings') {
|
||||
return {
|
||||
@@ -779,18 +817,6 @@ export default function StatusBar() {
|
||||
toast,
|
||||
])
|
||||
|
||||
const handleDynamicPortChange = useCallback(
|
||||
(port: number) => {
|
||||
if (forwardSaving || socksRunning) return
|
||||
const nextPort = resolveDynamicPort(port)
|
||||
const next = forwardRules.map((rule) =>
|
||||
rule.type === 'dynamic' ? { ...rule, localPort: nextPort } : rule
|
||||
)
|
||||
void persistForwardRules(next)
|
||||
},
|
||||
[forwardRules, forwardSaving, persistForwardRules, socksRunning]
|
||||
)
|
||||
|
||||
const handleCopySocks = useCallback(
|
||||
async (event: { preventDefault: () => void }) => {
|
||||
event.preventDefault()
|
||||
@@ -825,28 +851,6 @@ export default function StatusBar() {
|
||||
const statusCfg = STATUS_CONFIG[statusKey] ?? STATUS_CONFIG.disconnected
|
||||
const statusLabel = t(statusCfg.labelKey)
|
||||
|
||||
const tabKindLabel = useMemo(() => {
|
||||
if (!tabKind || tabKind === 'welcome' || tabKind === 'settings') return null
|
||||
switch (tabKind) {
|
||||
case 'terminal':
|
||||
return t('common:tab.kind.terminal')
|
||||
case 'sftp':
|
||||
return t('common:tab.kind.sftp')
|
||||
case 'portforward':
|
||||
return t('common:tab.kind.portforward')
|
||||
case 'serial':
|
||||
return t('common:tab.kind.serial')
|
||||
case 'rdp':
|
||||
return t('common:tab.kind.rdp')
|
||||
case 'vnc':
|
||||
return t('common:tab.kind.vnc')
|
||||
case 'batch':
|
||||
return t('common:tab.kind.batch')
|
||||
default:
|
||||
return tabKind
|
||||
}
|
||||
}, [tabKind, t])
|
||||
|
||||
const transferLabel =
|
||||
stats && stats.bytesTransferred > 0 ? `↑↓ ${formatBytes(stats.bytesTransferred)}` : null
|
||||
|
||||
@@ -855,7 +859,7 @@ export default function StatusBar() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={togglePanel}
|
||||
className={`-ml-1 flex cursor-pointer items-center gap-1.5 rounded-md px-1.5 py-0.5 transition-colors
|
||||
className={`-ml-1 flex shrink-0 cursor-pointer items-center gap-1.5 whitespace-nowrap rounded-md px-1.5 py-0.5 transition-colors
|
||||
hover:bg-dark-700/70 group
|
||||
${statusCfg.colorClass} ${statusCfg.animateClass}`}
|
||||
title={t('common:connectionPanel.title')}
|
||||
@@ -870,11 +874,6 @@ export default function StatusBar() {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{tabKindLabel && (
|
||||
<span className="bg-dark-700/80 text-txt-faint ml-1.5 rounded px-1.5 py-px text-[10px] capitalize">
|
||||
{tabKindLabel}
|
||||
</span>
|
||||
)}
|
||||
{totalConnections > 0 && (
|
||||
<span className="bg-accent-blue/20 ml-1 rounded-full px-1.5 py-px text-[10px] font-medium tabular-nums text-accent-blue">
|
||||
{totalConnections}
|
||||
@@ -892,7 +891,7 @@ export default function StatusBar() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex-1" />
|
||||
<div data-statusbar-spacer className="min-w-2 flex-1" />
|
||||
|
||||
{socksAvailable && (
|
||||
<button
|
||||
@@ -907,7 +906,7 @@ export default function StatusBar() {
|
||||
}
|
||||
aria-label={t('common:statusBar.forwardOpenIdle', { port: dynamicListenPort })}
|
||||
aria-pressed={socksRunning}
|
||||
className={`mr-2 flex items-center gap-1 rounded-md px-1.5 py-0.5 transition-colors disabled:opacity-50
|
||||
className={`mr-2 flex shrink-0 items-center gap-1 whitespace-nowrap rounded-md px-1.5 py-0.5 transition-colors disabled:opacity-50
|
||||
${
|
||||
socksRunning
|
||||
? 'bg-accent-blue/20 text-accent-blue hover:bg-accent-blue/30'
|
||||
@@ -950,7 +949,6 @@ export default function StatusBar() {
|
||||
onStartRule={(rule) => void handleStartRule(rule)}
|
||||
onStopRule={(rule) => void handleStopRule(rule)}
|
||||
onDeleteRule={(rule) => void handleDeleteRule(rule)}
|
||||
onDynamicPortChange={handleDynamicPortChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user