From cee5e9e0e229bffc2596db87e31f0ddcd79ad391 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 18 Apr 2026 22:16:36 +0800 Subject: [PATCH] feat(web): show active sandbox details in Box status popover Display sandbox count and a detailed list of active sessions including session ID, image, backend, resources (CPU/memory), network mode, and last used time. Fetched from GET /api/v1/box/sessions in parallel. Includes i18n for all 8 supported languages. --- web/src/app/home/plugins/page.tsx | 68 ++++++++++++++++++++++++- web/src/app/infra/entities/api/index.ts | 14 +++++ web/src/app/infra/http/BackendClient.ts | 5 ++ web/src/i18n/locales/en-US.ts | 5 ++ web/src/i18n/locales/es-ES.ts | 5 ++ web/src/i18n/locales/ja-JP.ts | 5 ++ web/src/i18n/locales/ru-RU.ts | 5 ++ web/src/i18n/locales/th-TH.ts | 5 ++ web/src/i18n/locales/vi-VN.ts | 5 ++ web/src/i18n/locales/zh-Hans.ts | 5 ++ web/src/i18n/locales/zh-Hant.ts | 5 ++ 11 files changed, 126 insertions(+), 1 deletion(-) diff --git a/web/src/app/home/plugins/page.tsx b/web/src/app/home/plugins/page.tsx index c3acf723..12979109 100644 --- a/web/src/app/home/plugins/page.tsx +++ b/web/src/app/home/plugins/page.tsx @@ -20,6 +20,8 @@ import { Box, CircleCheck, CircleX, + Container, + Clock, } from 'lucide-react'; import { DropdownMenu, @@ -49,6 +51,7 @@ import { systemInfo } from '@/app/infra/http/HttpClient'; import { ApiRespPluginSystemStatus, ApiRespBoxStatus, + BoxSessionInfo, } from '@/app/infra/entities/api'; import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext'; import { @@ -140,6 +143,7 @@ function PluginListView() { const [copiedDebugUrl, setCopiedDebugUrl] = useState(false); const [copiedDebugKey, setCopiedDebugKey] = useState(false); const [boxStatus, setBoxStatus] = useState(null); + const [boxSessions, setBoxSessions] = useState([]); useEffect(() => { const fetchPluginSystemStatus = async () => { @@ -461,12 +465,14 @@ function PluginListView() { const handleShowDebugInfo = async () => { try { - const [info, box] = await Promise.all([ + const [info, box, sessions] = await Promise.all([ httpClient.getPluginDebugInfo(), httpClient.getBoxStatus().catch(() => null), + httpClient.getBoxSessions().catch(() => [] as BoxSessionInfo[]), ]); setDebugInfo(info); setBoxStatus(box); + setBoxSessions(sessions); setDebugPopoverOpen(true); } catch (error) { console.error('Failed to fetch debug info:', error); @@ -702,6 +708,14 @@ function PluginListView() { {boxStatus.profile} +
+ + {t('plugins.boxSandboxes')}: + + + {boxStatus.active_sessions ?? 0} + +
{(boxStatus.recent_error_count ?? 0) > 0 && (
@@ -712,6 +726,58 @@ function PluginListView() {
)} + {boxSessions.length > 0 && ( +
+ {boxSessions.map((session) => ( +
+
+ + + {session.session_id} + +
+
+ + {t('plugins.boxSessionImage')}:{' '} + + {session.image.split(':').pop() || + session.image} + + + + {t('plugins.boxSessionBackend')}:{' '} + + {session.backend_name} + + + + {t('plugins.boxSessionResources')}:{' '} + + {session.cpus} CPU / {session.memory_mb}MB + + + + {t('plugins.boxSessionNetwork')}:{' '} + + {session.network} + + +
+
+ + + {new Date( + session.last_used_at, + ).toLocaleString()} + +
+
+ ))} +
+ )} ) : (

diff --git a/web/src/app/infra/entities/api/index.ts b/web/src/app/infra/entities/api/index.ts index 24e360cd..d6896942 100644 --- a/web/src/app/infra/entities/api/index.ts +++ b/web/src/app/infra/entities/api/index.ts @@ -364,6 +364,20 @@ export interface ApiRespBoxStatus { session_ttl_sec?: number; } +export interface BoxSessionInfo { + session_id: string; + backend_name: string; + image: string; + network: string; + host_path: string | null; + host_path_mode: string; + mount_path: string; + cpus: number; + memory_mb: number; + created_at: string; + last_used_at: string; +} + export interface ApiRespAsyncTasks { tasks: AsyncTask[]; } diff --git a/web/src/app/infra/http/BackendClient.ts b/web/src/app/infra/http/BackendClient.ts index 643f1bb1..1b12119c 100644 --- a/web/src/app/infra/http/BackendClient.ts +++ b/web/src/app/infra/http/BackendClient.ts @@ -36,6 +36,7 @@ import { RerankModel, ApiRespPluginSystemStatus, ApiRespBoxStatus, + BoxSessionInfo, ApiRespMCPServers, ApiRespMCPServer, MCPServer, @@ -894,6 +895,10 @@ export class BackendClient extends BaseHttpClient { return this.get('/api/v1/box/status'); } + public getBoxSessions(): Promise { + return this.get('/api/v1/box/sessions'); + } + // ============ User API ============ public checkIfInited(): Promise<{ initialized: boolean }> { return this.get('/api/v1/user/init'); diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 3cb7867f..2f9b32aa 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -473,7 +473,12 @@ const enUS = { boxUnavailable: 'Unavailable', boxBackend: 'Backend', boxProfile: 'Profile', + boxSandboxes: 'Sandboxes', boxErrors: 'Errors', + boxSessionImage: 'Image', + boxSessionBackend: 'Backend', + boxSessionResources: 'Resources', + boxSessionNetwork: 'Network', boxStatusLoadFailed: 'Failed to load Box status', failedToGetDebugInfo: 'Failed to get debug information', copiedToClipboard: 'Copied to clipboard', diff --git a/web/src/i18n/locales/es-ES.ts b/web/src/i18n/locales/es-ES.ts index 890bd188..6d509237 100644 --- a/web/src/i18n/locales/es-ES.ts +++ b/web/src/i18n/locales/es-ES.ts @@ -485,7 +485,12 @@ const esES = { boxUnavailable: 'No disponible', boxBackend: 'Backend', boxProfile: 'Perfil', + boxSandboxes: 'Sandboxes', boxErrors: 'Errores', + boxSessionImage: 'Imagen', + boxSessionBackend: 'Backend', + boxSessionResources: 'Recursos', + boxSessionNetwork: 'Red', boxStatusLoadFailed: 'Error al cargar el estado de Box', failedToGetDebugInfo: 'Error al obtener la información de depuración', copiedToClipboard: 'Copiado al portapapeles', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 022569e8..38988aba 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -478,7 +478,12 @@ const jaJP = { boxUnavailable: '利用不可', boxBackend: 'バックエンド', boxProfile: 'プロファイル', + boxSandboxes: 'サンドボックス', boxErrors: 'エラー', + boxSessionImage: 'イメージ', + boxSessionBackend: 'バックエンド', + boxSessionResources: 'リソース', + boxSessionNetwork: 'ネットワーク', boxStatusLoadFailed: 'Box ステータスの読み込みに失敗しました', failedToGetDebugInfo: 'デバッグ情報の取得に失敗しました', copiedToClipboard: 'クリップボードにコピーしました', diff --git a/web/src/i18n/locales/ru-RU.ts b/web/src/i18n/locales/ru-RU.ts index aa7636bc..48b32ea5 100644 --- a/web/src/i18n/locales/ru-RU.ts +++ b/web/src/i18n/locales/ru-RU.ts @@ -481,7 +481,12 @@ const ruRU = { boxUnavailable: 'Недоступно', boxBackend: 'Бэкенд', boxProfile: 'Профиль', + boxSandboxes: 'Песочницы', boxErrors: 'Ошибки', + boxSessionImage: 'Образ', + boxSessionBackend: 'Бэкенд', + boxSessionResources: 'Ресурсы', + boxSessionNetwork: 'Сеть', boxStatusLoadFailed: 'Не удалось загрузить статус Box', failedToGetDebugInfo: 'Не удалось получить отладочную информацию', copiedToClipboard: 'Скопировано в буфер обмена', diff --git a/web/src/i18n/locales/th-TH.ts b/web/src/i18n/locales/th-TH.ts index fcf6f6c6..35573da3 100644 --- a/web/src/i18n/locales/th-TH.ts +++ b/web/src/i18n/locales/th-TH.ts @@ -468,7 +468,12 @@ const thTH = { boxUnavailable: 'ไม่พร้อมใช้งาน', boxBackend: 'แบ็กเอนด์', boxProfile: 'โปรไฟล์', + boxSandboxes: 'แซนด์บ็อกซ์', boxErrors: 'ข้อผิดพลาด', + boxSessionImage: 'อิมเมจ', + boxSessionBackend: 'แบ็กเอนด์', + boxSessionResources: 'ทรัพยากร', + boxSessionNetwork: 'เครือข่าย', boxStatusLoadFailed: 'โหลดสถานะ Box ล้มเหลว', failedToGetDebugInfo: 'ไม่สามารถดึงข้อมูลดีบักได้', copiedToClipboard: 'คัดลอกไปยังคลิปบอร์ดแล้ว', diff --git a/web/src/i18n/locales/vi-VN.ts b/web/src/i18n/locales/vi-VN.ts index 83851b85..a7a0c179 100644 --- a/web/src/i18n/locales/vi-VN.ts +++ b/web/src/i18n/locales/vi-VN.ts @@ -478,7 +478,12 @@ const viVN = { boxUnavailable: 'Không khả dụng', boxBackend: 'Backend', boxProfile: 'Hồ sơ', + boxSandboxes: 'Sandbox', boxErrors: 'Lỗi', + boxSessionImage: 'Image', + boxSessionBackend: 'Backend', + boxSessionResources: 'Tài nguyên', + boxSessionNetwork: 'Mạng', boxStatusLoadFailed: 'Tải trạng thái Box thất bại', failedToGetDebugInfo: 'Lấy thông tin gỡ lỗi thất bại', copiedToClipboard: 'Đã sao chép vào clipboard', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 2ff50369..c0fa2b97 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -452,7 +452,12 @@ const zhHans = { boxUnavailable: '不可用', boxBackend: '后端', boxProfile: '配置', + boxSandboxes: '沙箱数', boxErrors: '错误数', + boxSessionImage: '镜像', + boxSessionBackend: '后端', + boxSessionResources: '资源', + boxSessionNetwork: '网络', boxStatusLoadFailed: '加载 Box 状态失败', failedToGetDebugInfo: '获取调试信息失败', copiedToClipboard: '已复制到剪贴板', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 6109137d..128dee4f 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -453,7 +453,12 @@ const zhHant = { boxUnavailable: '不可用', boxBackend: '後端', boxProfile: '設定檔', + boxSandboxes: '沙箱數', boxErrors: '錯誤數', + boxSessionImage: '映像檔', + boxSessionBackend: '後端', + boxSessionResources: '資源', + boxSessionNetwork: '網路', boxStatusLoadFailed: '載入 Box 狀態失敗', failedToGetDebugInfo: '取得偵錯資訊失敗', copiedToClipboard: '已複製到剪貼簿',