feat(web): show Box runtime status in plugin debug info popover

Add Box status section to the debug info popover on the plugin list page,
displaying connection status, backend info, profile, active sessions,
and recent error count. Fetched from GET /api/v1/box/status in parallel
with plugin debug info. Includes i18n for all 8 supported languages.
This commit is contained in:
Junyan Qin
2026-04-18 00:03:18 +08:00
committed by WangCham
parent aa40151964
commit e2d555a945
11 changed files with 180 additions and 2 deletions

View File

@@ -17,6 +17,10 @@ import {
Check,
Bug,
Unlink,
Box,
CircleCheck,
CircleX,
Activity,
} from 'lucide-react';
import {
DropdownMenu,
@@ -43,7 +47,10 @@ import { httpClient } from '@/app/infra/http/HttpClient';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import { systemInfo } from '@/app/infra/http/HttpClient';
import { ApiRespPluginSystemStatus } from '@/app/infra/entities/api';
import {
ApiRespPluginSystemStatus,
ApiRespBoxStatus,
} from '@/app/infra/entities/api';
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
import {
PluginInstallTaskQueue,
@@ -133,6 +140,7 @@ function PluginListView() {
const [debugPopoverOpen, setDebugPopoverOpen] = useState(false);
const [copiedDebugUrl, setCopiedDebugUrl] = useState(false);
const [copiedDebugKey, setCopiedDebugKey] = useState(false);
const [boxStatus, setBoxStatus] = useState<ApiRespBoxStatus | null>(null);
useEffect(() => {
const fetchPluginSystemStatus = async () => {
@@ -454,8 +462,12 @@ function PluginListView() {
const handleShowDebugInfo = async () => {
try {
const info = await httpClient.getPluginDebugInfo();
const [info, box] = await Promise.all([
httpClient.getPluginDebugInfo(),
httpClient.getBoxStatus().catch(() => null),
]);
setDebugInfo(info);
setBoxStatus(box);
setDebugPopoverOpen(true);
} catch (error) {
console.error('Failed to fetch debug info:', error);
@@ -645,6 +657,82 @@ function PluginListView() {
</p>
)}
</div>
{/* Box Runtime Status */}
<div className="pt-2 border-t">
<div className="flex items-center gap-2 pb-2">
<Box className="w-4 h-4" />
<h4 className="font-semibold text-sm">
{t('plugins.boxStatusTitle')}
</h4>
</div>
{boxStatus ? (
<div className="space-y-1.5 text-xs">
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[80px]">
{t('plugins.boxStatus')}:
</span>
{boxStatus.available ? (
<span className="flex items-center gap-1 text-green-600">
<CircleCheck className="w-3.5 h-3.5" />
{t('plugins.boxConnected')}
</span>
) : (
<span className="flex items-center gap-1 text-red-500">
<CircleX className="w-3.5 h-3.5" />
{t('plugins.boxUnavailable')}
</span>
)}
</div>
{boxStatus.backend && (
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[80px]">
{t('plugins.boxBackend')}:
</span>
<span className="font-mono">
{boxStatus.backend.name}
{boxStatus.backend.available
? ''
: ` (${t('plugins.boxUnavailable')})`}
</span>
</div>
)}
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[80px]">
{t('plugins.boxProfile')}:
</span>
<span className="font-mono">{boxStatus.profile}</span>
</div>
{boxStatus.available && (
<>
<div className="flex items-center gap-2">
<span className="text-muted-foreground min-w-[80px]">
{t('plugins.boxSessions')}:
</span>
<span className="flex items-center gap-1 font-mono">
<Activity className="w-3 h-3" />
{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]">
{t('plugins.boxErrors')}:
</span>
<span className="text-amber-600 font-mono">
{boxStatus.recent_error_count}
</span>
</div>
)}
</>
)}
</div>
) : (
<p className="text-xs text-muted-foreground">
{t('plugins.boxStatusLoadFailed')}
</p>
)}
</div>
</div>
</PopoverContent>
</Popover>

View File

@@ -351,6 +351,19 @@ export interface ApiRespPluginSystemStatus {
plugin_connector_error: string;
}
export interface ApiRespBoxStatus {
available: boolean;
profile: string;
recent_error_count: number;
backend?: {
name: string;
available: boolean;
};
active_sessions?: number;
managed_processes?: number;
session_ttl_sec?: number;
}
export interface ApiRespAsyncTasks {
tasks: AsyncTask[];
}

View File

@@ -35,6 +35,7 @@ import {
ApiRespProviderRerankModel,
RerankModel,
ApiRespPluginSystemStatus,
ApiRespBoxStatus,
ApiRespMCPServers,
ApiRespMCPServer,
MCPServer,
@@ -889,6 +890,10 @@ export class BackendClient extends BaseHttpClient {
return this.get('/api/v1/plugins/debug-info');
}
public getBoxStatus(): Promise<ApiRespBoxStatus> {
return this.get('/api/v1/box/status');
}
// ============ User API ============
public checkIfInited(): Promise<{ initialized: boolean }> {
return this.get('/api/v1/user/init');