(null);
@@ -325,30 +373,50 @@ export function ProxyStatusWidget() {
{t('proxyStatusWidget.active')}
-
-
-
-
-
-
{remoteDisplayHost}
+
+
+
-
- {t('proxyStatusWidget.trafficAutoRouted')}
-
-
updateRouting.mutate(strategy)}
- onApplyAffinity={(data) => updateSessionAffinity.mutate(data)}
- />
+ {!isCollapsed && (
+ <>
+
+
+ {remoteDisplayHost}
+
+
+ {t('proxyStatusWidget.trafficAutoRouted')}
+
+
+
+ updateRouting.mutate(strategy)}
+ onApplyAffinity={(data) => updateSessionAffinity.mutate(data)}
+ />
+ >
+ )}
);
}
@@ -413,9 +481,14 @@ export function ProxyStatusWidget() {
? t('proxyStatusWidget.tooltipClose')
: t('proxyStatusWidget.tooltipVersionSettings')
}
- onClick={() => setIsExpanded(!isExpanded)}
+ onClick={handleToggleVersionSettings}
className={isExpanded ? 'bg-muted' : undefined}
/>
+
@@ -458,7 +531,7 @@ export function ProxyStatusWidget() {
)}
{/* Stats row when running */}
- {isRunning && status && (
+ {!isCollapsed && isRunning && status && (
{t('proxyStatusWidget.port', { port: status.port })}
@@ -478,35 +551,39 @@ export function ProxyStatusWidget() {
)}
- {/* Sync status row */}
-
- {isSyncConfigured ? (
-
- ) : (
-
- )}
-
- {syncStatusText}
-
-
+ {!isCollapsed && (
+ <>
+ {/* Sync status row */}
+
+ {isSyncConfigured ? (
+
+ ) : (
+
+ )}
+
+ {syncStatusText}
+
+
- updateRouting.mutate(strategy)}
- onApplyAffinity={(data) => updateSessionAffinity.mutate(data)}
- />
+ updateRouting.mutate(strategy)}
+ onApplyAffinity={(data) => updateSessionAffinity.mutate(data)}
+ />
+ >
+ )}
{/* Expanded section: Version Management (available even when not running) */}
diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts
index 6ac2da8a..d8bdc792 100644
--- a/ui/src/lib/i18n.ts
+++ b/ui/src/lib/i18n.ts
@@ -428,6 +428,8 @@ const resources = {
tooltipStop: 'Stop',
tooltipClose: 'Close',
tooltipVersionSettings: 'Version settings',
+ tooltipCollapseWidget: 'Collapse',
+ tooltipExpandWidget: 'Expand',
versionManagement: 'Version Management',
selectVersionPlaceholder: 'Select version to install...',
stable: '(stable)',
@@ -3180,6 +3182,8 @@ const resources = {
tooltipStop: 'Parar',
tooltipClose: 'Fechar',
tooltipVersionSettings: 'Configurações de versão',
+ tooltipCollapseWidget: 'Recolher',
+ tooltipExpandWidget: 'Expandir',
versionManagement: 'Gerenciamento de Versão',
selectVersionPlaceholder: 'Selecione a versão a instalar...',
stable: '(estável)',
@@ -5962,6 +5966,8 @@ const resources = {
tooltipStop: '停止',
tooltipClose: '关闭',
tooltipVersionSettings: '版本设置',
+ tooltipCollapseWidget: '收起',
+ tooltipExpandWidget: '展开',
versionManagement: '版本管理',
selectVersionPlaceholder: '选择要安装的版本...',
stable: '(稳定版)',
@@ -8595,6 +8601,8 @@ const resources = {
tooltipStop: 'Dừng lại',
tooltipClose: 'Đóng',
tooltipVersionSettings: 'Cài đặt phiên bản',
+ tooltipCollapseWidget: 'Thu gọn',
+ tooltipExpandWidget: 'Mở rộng',
versionManagement: 'Quản lý phiên bản',
selectVersionPlaceholder: 'Chọn phiên bản để cài đặt...',
stable: '(ổn định)',
@@ -11329,6 +11337,8 @@ const resources = {
tooltipStop: '停止',
tooltipClose: '閉じる',
tooltipVersionSettings: 'バージョン設定',
+ tooltipCollapseWidget: '折りたたむ',
+ tooltipExpandWidget: '展開',
versionManagement: 'バージョン管理',
selectVersionPlaceholder: 'インストールするバージョンを選択...',
stable: '(安定版)',
@@ -14087,6 +14097,8 @@ const resources = {
tooltipStop: '중지',
tooltipClose: '닫기',
tooltipVersionSettings: '버전 설정',
+ tooltipCollapseWidget: '접기',
+ tooltipExpandWidget: '펼치기',
versionManagement: '버전 관리',
selectVersionPlaceholder: '설치할 버전 선택...',
stable: '(안정)',
diff --git a/ui/tests/unit/components/monitoring/proxy-status-widget.test.tsx b/ui/tests/unit/components/monitoring/proxy-status-widget.test.tsx
new file mode 100644
index 00000000..55893a05
--- /dev/null
+++ b/ui/tests/unit/components/monitoring/proxy-status-widget.test.tsx
@@ -0,0 +1,97 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { fireEvent, render, screen } from '../../../setup/test-utils';
+
+const COLLAPSED_STORAGE_KEY = 'cliproxy-status-widget-collapsed';
+
+const mocks = vi.hoisted(() => ({
+ noopMutation: () => ({ mutate: vi.fn(), isPending: false }),
+}));
+
+vi.mock('@/hooks/use-cliproxy', () => ({
+ useProxyStatus: () => ({
+ data: { running: true, port: 8317, sessionCount: 1 },
+ isLoading: false,
+ }),
+ useCliproxyUpdateCheck: () => ({ data: { backendLabel: 'CLIProxy', currentVersion: '7.2.137' } }),
+ useCliproxyVersions: () => ({ data: undefined, isLoading: false }),
+ useCliproxyRoutingStrategy: () => ({ data: undefined, isLoading: false, error: null }),
+ useCliproxySessionAffinity: () => ({ data: undefined, isLoading: false, error: null }),
+ useUpdateCliproxyRoutingStrategy: mocks.noopMutation,
+ useUpdateCliproxySessionAffinity: mocks.noopMutation,
+ useStartProxy: mocks.noopMutation,
+ useStopProxy: mocks.noopMutation,
+ useRestartProxy: mocks.noopMutation,
+ useInstallVersion: mocks.noopMutation,
+}));
+
+vi.mock('@/hooks/use-cliproxy-sync', () => ({
+ useSyncStatus: () => ({ data: { configured: true } }),
+ useExecuteSync: () => ({ mutate: vi.fn(), isPending: false }),
+}));
+
+vi.mock('@/lib/api-client', () => ({
+ api: {
+ cliproxyServer: {
+ get: () => Promise.resolve({ remote: { enabled: false, host: '' } }),
+ },
+ },
+}));
+
+vi.mock('@/components/cliproxy/routing-guidance-card', () => ({
+ RoutingGuidanceCard: () => ,
+}));
+
+import { ProxyStatusWidget } from '@/components/monitoring/proxy-status-widget';
+
+describe('ProxyStatusWidget collapse', () => {
+ beforeEach(() => {
+ vi.mocked(window.localStorage.getItem).mockReturnValue(null);
+ vi.mocked(window.localStorage.setItem).mockClear();
+ });
+
+ it('shows the routing and status detail by default', () => {
+ render();
+
+ expect(screen.getByTestId('routing-guidance-card')).toBeInTheDocument();
+ expect(screen.getByText('v7.2.137')).toBeInTheDocument();
+ });
+
+ it('hides the detail and persists the preference when collapsed', () => {
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: 'Collapse' }));
+
+ expect(screen.queryByTestId('routing-guidance-card')).toBeNull();
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(COLLAPSED_STORAGE_KEY, 'true');
+ });
+
+ it('keeps the status row and version visible while collapsed', () => {
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: 'Collapse' }));
+
+ expect(screen.getByText('CLIProxy')).toBeInTheDocument();
+ expect(screen.getByText('v7.2.137')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Expand' })).toBeInTheDocument();
+ });
+
+ it('starts collapsed when the stored preference says so', () => {
+ vi.mocked(window.localStorage.getItem).mockReturnValue('true');
+
+ render();
+
+ expect(screen.queryByTestId('routing-guidance-card')).toBeNull();
+ expect(screen.getByRole('button', { name: 'Expand' })).toBeInTheDocument();
+ });
+
+ it('reveals the detail again when version settings are opened while collapsed', () => {
+ vi.mocked(window.localStorage.getItem).mockReturnValue('true');
+
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: 'Version settings' }));
+
+ expect(screen.getByTestId('routing-guidance-card')).toBeInTheDocument();
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(COLLAPSED_STORAGE_KEY, 'false');
+ });
+});