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:
Junyan Qin
2026-04-18 22:16:36 +08:00
committed by WangCham
parent 7e50063731
commit cee5e9e0e2
11 changed files with 126 additions and 1 deletions

View File

@@ -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">

View File

@@ -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[];
}

View File

@@ -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');