mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 03:55:55 +00:00
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.
This commit is contained in:
@@ -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<ApiRespBoxStatus | null>(null);
|
||||
const [boxSessions, setBoxSessions] = useState<BoxSessionInfo[]>([]);
|
||||
|
||||
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() {
|
||||
</span>
|
||||
<span className="font-mono">{boxStatus.profile}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground min-w-[80px]">
|
||||
{t('plugins.boxSandboxes')}:
|
||||
</span>
|
||||
<span className="font-mono">
|
||||
{boxStatus.active_sessions ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
{(boxStatus.recent_error_count ?? 0) > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground min-w-[80px]">
|
||||
@@ -712,6 +726,58 @@ function PluginListView() {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{boxSessions.length > 0 && (
|
||||
<div className="pt-1.5 mt-1.5 border-t border-dashed space-y-2">
|
||||
{boxSessions.map((session) => (
|
||||
<div
|
||||
key={session.session_id}
|
||||
className="rounded border p-2 space-y-1"
|
||||
>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Container className="w-3 h-3 text-muted-foreground" />
|
||||
<span className="font-mono font-medium truncate">
|
||||
{session.session_id}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-3 gap-y-0.5 text-muted-foreground">
|
||||
<span>
|
||||
{t('plugins.boxSessionImage')}:{' '}
|
||||
<span className="text-foreground font-mono">
|
||||
{session.image.split(':').pop() ||
|
||||
session.image}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{t('plugins.boxSessionBackend')}:{' '}
|
||||
<span className="text-foreground font-mono">
|
||||
{session.backend_name}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{t('plugins.boxSessionResources')}:{' '}
|
||||
<span className="text-foreground font-mono">
|
||||
{session.cpus} CPU / {session.memory_mb}MB
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
{t('plugins.boxSessionNetwork')}:{' '}
|
||||
<span className="text-foreground font-mono">
|
||||
{session.network}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<Clock className="w-3 h-3" />
|
||||
<span>
|
||||
{new Date(
|
||||
session.last_used_at,
|
||||
).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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<BoxSessionInfo[]> {
|
||||
return this.get('/api/v1/box/sessions');
|
||||
}
|
||||
|
||||
// ============ User API ============
|
||||
public checkIfInited(): Promise<{ initialized: boolean }> {
|
||||
return this.get('/api/v1/user/init');
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -478,7 +478,12 @@ const jaJP = {
|
||||
boxUnavailable: '利用不可',
|
||||
boxBackend: 'バックエンド',
|
||||
boxProfile: 'プロファイル',
|
||||
boxSandboxes: 'サンドボックス',
|
||||
boxErrors: 'エラー',
|
||||
boxSessionImage: 'イメージ',
|
||||
boxSessionBackend: 'バックエンド',
|
||||
boxSessionResources: 'リソース',
|
||||
boxSessionNetwork: 'ネットワーク',
|
||||
boxStatusLoadFailed: 'Box ステータスの読み込みに失敗しました',
|
||||
failedToGetDebugInfo: 'デバッグ情報の取得に失敗しました',
|
||||
copiedToClipboard: 'クリップボードにコピーしました',
|
||||
|
||||
@@ -481,7 +481,12 @@ const ruRU = {
|
||||
boxUnavailable: 'Недоступно',
|
||||
boxBackend: 'Бэкенд',
|
||||
boxProfile: 'Профиль',
|
||||
boxSandboxes: 'Песочницы',
|
||||
boxErrors: 'Ошибки',
|
||||
boxSessionImage: 'Образ',
|
||||
boxSessionBackend: 'Бэкенд',
|
||||
boxSessionResources: 'Ресурсы',
|
||||
boxSessionNetwork: 'Сеть',
|
||||
boxStatusLoadFailed: 'Не удалось загрузить статус Box',
|
||||
failedToGetDebugInfo: 'Не удалось получить отладочную информацию',
|
||||
copiedToClipboard: 'Скопировано в буфер обмена',
|
||||
|
||||
@@ -468,7 +468,12 @@ const thTH = {
|
||||
boxUnavailable: 'ไม่พร้อมใช้งาน',
|
||||
boxBackend: 'แบ็กเอนด์',
|
||||
boxProfile: 'โปรไฟล์',
|
||||
boxSandboxes: 'แซนด์บ็อกซ์',
|
||||
boxErrors: 'ข้อผิดพลาด',
|
||||
boxSessionImage: 'อิมเมจ',
|
||||
boxSessionBackend: 'แบ็กเอนด์',
|
||||
boxSessionResources: 'ทรัพยากร',
|
||||
boxSessionNetwork: 'เครือข่าย',
|
||||
boxStatusLoadFailed: 'โหลดสถานะ Box ล้มเหลว',
|
||||
failedToGetDebugInfo: 'ไม่สามารถดึงข้อมูลดีบักได้',
|
||||
copiedToClipboard: 'คัดลอกไปยังคลิปบอร์ดแล้ว',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -452,7 +452,12 @@ const zhHans = {
|
||||
boxUnavailable: '不可用',
|
||||
boxBackend: '后端',
|
||||
boxProfile: '配置',
|
||||
boxSandboxes: '沙箱数',
|
||||
boxErrors: '错误数',
|
||||
boxSessionImage: '镜像',
|
||||
boxSessionBackend: '后端',
|
||||
boxSessionResources: '资源',
|
||||
boxSessionNetwork: '网络',
|
||||
boxStatusLoadFailed: '加载 Box 状态失败',
|
||||
failedToGetDebugInfo: '获取调试信息失败',
|
||||
copiedToClipboard: '已复制到剪贴板',
|
||||
|
||||
@@ -453,7 +453,12 @@ const zhHant = {
|
||||
boxUnavailable: '不可用',
|
||||
boxBackend: '後端',
|
||||
boxProfile: '設定檔',
|
||||
boxSandboxes: '沙箱數',
|
||||
boxErrors: '錯誤數',
|
||||
boxSessionImage: '映像檔',
|
||||
boxSessionBackend: '後端',
|
||||
boxSessionResources: '資源',
|
||||
boxSessionNetwork: '網路',
|
||||
boxStatusLoadFailed: '載入 Box 狀態失敗',
|
||||
failedToGetDebugInfo: '取得偵錯資訊失敗',
|
||||
copiedToClipboard: '已複製到剪貼簿',
|
||||
|
||||
Reference in New Issue
Block a user