diff --git a/src/components/connection/ForwardRuleDrawer.tsx b/src/components/connection/ForwardRuleDrawer.tsx
index 706bccb..31a54f1 100644
--- a/src/components/connection/ForwardRuleDrawer.tsx
+++ b/src/components/connection/ForwardRuleDrawer.tsx
@@ -2,7 +2,7 @@
* Right drawer for SSH port forwarding (SessionDialog-style).
* Portaled to document.body so TabContent overflow-hidden cannot clip it.
*
- * Dynamic is a single SOCKS5 on 127.0.0.1 (default port 1080, editable when stopped).
+ * Dynamic is a single SOCKS5 on 127.0.0.1 (default port 1080; port set in the form below).
* Local / Remote rules appear in the list and can be added repeatedly.
*/
@@ -49,8 +49,6 @@ interface ForwardRuleDrawerProps {
onStartRule?: (rule: ForwardRule) => void
onStopRule?: (rule: ForwardRule) => void
onDeleteRule?: (rule: ForwardRule) => void
- /** Persist dynamic listen port while stopped (list mode). */
- onDynamicPortChange?: (port: number) => void
}
const EMPTY_DRAFT: ForwardRuleDraft = {
@@ -103,7 +101,6 @@ export default function ForwardRuleDrawer({
onStartRule,
onStopRule,
onDeleteRule,
- onDynamicPortChange,
}: ForwardRuleDrawerProps) {
const { t } = useTranslation('common')
const type = (draft.type || 'local') as ForwardRuleType
@@ -244,25 +241,6 @@ export default function ForwardRuleDrawer({
127.0.0.1:{dynamicPort}
- {!dynamicRunning && dynamicRule && onDynamicPortChange && (
-
- {t('portForward.localListenPort')}
- onDynamicPortChange(Number(e.target.value))}
- className="ui-input h-7 w-24 py-0 font-mono text-[11px]"
- inputMode="numeric"
- title={t('portForward.dynamicPortHint')}
- />
-
- )}
-
- {t('portForward.dynamicSingleHint')}
-
{dynamicRule && dynamicRunning && onStopRule && (
diff --git a/src/components/layout/StatusBar.tsx b/src/components/layout/StatusBar.tsx
index 5e7614c..06e3027 100644
--- a/src/components/layout/StatusBar.tsx
+++ b/src/components/layout/StatusBar.tsx
@@ -209,10 +209,22 @@ function RemoteHostMetricsStrip({
transferLabel?: string | null
}) {
const { t } = useTranslation('common')
+ const stripRef = useRef
(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('[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('[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 (
{osLabel && (
-
+
{osLabel}
)}
@@ -265,8 +303,8 @@ function RemoteHostMetricsStrip({
{transferLabel}
)}
- {disks.map((d) => (
-
+ {visibleDisks.map((d) => (
+
{shortMount(d.mount)} {d.percent}%
))}
@@ -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() {
>
)}
- {tabKindLabel && (
-
- {tabKindLabel}
-
- )}
{totalConnections > 0 && (
{totalConnections}
@@ -892,7 +891,7 @@ export default function StatusBar() {
/>
)}
-
+
{socksAvailable && (
void handleStartRule(rule)}
onStopRule={(rule) => void handleStopRule(rule)}
onDeleteRule={(rule) => void handleDeleteRule(rule)}
- onDynamicPortChange={handleDynamicPortChange}
/>
)