From b71f6908865a75370ce23c4511cb4a2368f4ff7b Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sun, 19 Apr 2026 13:48:26 +0800 Subject: [PATCH] feat(web): move runtime status to dashboard, clean up plugin debug popover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SystemStatusCards component to the monitoring dashboard showing Plugin Runtime and Box Runtime connection status with details (backend, profile, sandbox count). Remove all Box/session status from the plugin page debug popover — it now only shows debug URL and key. Includes i18n for all 8 supported languages. --- .../overview-cards/SystemStatusCards.tsx | 157 ++++++++++++++++++ web/src/app/home/monitoring/page.tsx | 4 + web/src/app/home/plugins/page.tsx | 144 +--------------- web/src/i18n/locales/en-US.ts | 10 ++ web/src/i18n/locales/es-ES.ts | 10 ++ web/src/i18n/locales/ja-JP.ts | 10 ++ web/src/i18n/locales/ru-RU.ts | 10 ++ web/src/i18n/locales/th-TH.ts | 10 ++ web/src/i18n/locales/vi-VN.ts | 10 ++ web/src/i18n/locales/zh-Hans.ts | 10 ++ web/src/i18n/locales/zh-Hant.ts | 10 ++ 11 files changed, 243 insertions(+), 142 deletions(-) create mode 100644 web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx diff --git a/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx b/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx new file mode 100644 index 00000000..5181fed1 --- /dev/null +++ b/web/src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Plug, Box, CircleCheck, CircleX, Loader2 } from 'lucide-react'; +import { + ApiRespPluginSystemStatus, + ApiRespBoxStatus, +} from '@/app/infra/entities/api'; +import { httpClient } from '@/app/infra/http/HttpClient'; + +interface StatusCardProps { + icon: React.ReactNode; + title: string; + connected: boolean | null; + connectedLabel: string; + disconnectedLabel: string; + details?: { label: string; value: string }[]; + loading: boolean; +} + +function StatusCard({ + icon, + title, + connected, + connectedLabel, + disconnectedLabel, + details, + loading, +}: StatusCardProps) { + return ( +
+
+ {icon} +
+
+

{title}

+ {loading ? ( +
+ +
+ ) : connected === null ? ( + + ) : connected ? ( +
+ + + {connectedLabel} + +
+ ) : ( +
+ + + {disconnectedLabel} + +
+ )} + {!loading && details && details.length > 0 && ( +
+ {details.map((d) => ( + + {d.label}:{' '} + {d.value} + + ))} +
+ )} +
+
+ ); +} + +export default function SystemStatusCards() { + const { t } = useTranslation(); + const [pluginStatus, setPluginStatus] = + useState(null); + const [boxStatus, setBoxStatus] = useState(null); + const [loading, setLoading] = useState(true); + + const fetchStatus = useCallback(async () => { + setLoading(true); + try { + const [plugin, box] = await Promise.all([ + httpClient.getPluginSystemStatus().catch(() => null), + httpClient.getBoxStatus().catch(() => null), + ]); + setPluginStatus(plugin); + setBoxStatus(box); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchStatus(); + }, [fetchStatus]); + + const pluginConnected = pluginStatus + ? pluginStatus.is_enable && pluginStatus.is_connected + : null; + + const pluginDetails: { label: string; value: string }[] = []; + if (pluginStatus && !pluginStatus.is_enable) { + pluginDetails.push({ + label: t('monitoring.statusDetail'), + value: t('monitoring.pluginDisabled'), + }); + } + + const boxConnected = boxStatus ? boxStatus.available : null; + + const boxDetails: { label: string; value: string }[] = []; + if (boxStatus) { + if (boxStatus.backend) { + boxDetails.push({ + label: t('monitoring.boxBackend'), + value: `${boxStatus.backend.name}`, + }); + } + boxDetails.push({ + label: t('monitoring.boxProfile'), + value: boxStatus.profile, + }); + if (boxStatus.available && boxStatus.active_sessions !== undefined) { + boxDetails.push({ + label: t('monitoring.boxSandboxes'), + value: String(boxStatus.active_sessions), + }); + } + } + + return ( +
+ } + title={t('monitoring.pluginRuntime')} + connected={pluginConnected} + connectedLabel={t('monitoring.connected')} + disconnectedLabel={ + pluginStatus && !pluginStatus.is_enable + ? t('monitoring.disabled') + : t('monitoring.disconnected') + } + details={pluginDetails} + loading={loading} + /> + } + title={t('monitoring.boxRuntime')} + connected={boxConnected} + connectedLabel={t('monitoring.connected')} + disconnectedLabel={t('monitoring.disconnected')} + details={boxDetails} + loading={loading} + /> +
+ ); +} diff --git a/web/src/app/home/monitoring/page.tsx b/web/src/app/home/monitoring/page.tsx index a86b83ae..387506bb 100644 --- a/web/src/app/home/monitoring/page.tsx +++ b/web/src/app/home/monitoring/page.tsx @@ -12,6 +12,7 @@ import { CheckCircle2, } from 'lucide-react'; import OverviewCards from './components/overview-cards/OverviewCards'; +import SystemStatusCards from './components/overview-cards/SystemStatusCards'; import MonitoringFilters from './components/filters/MonitoringFilters'; import { ExportDropdown } from './components/ExportDropdown'; import { useMonitoringFilters } from './hooks/useMonitoringFilters'; @@ -296,6 +297,9 @@ function MonitoringPageContent() { {/* Content Area */}
+ {/* System Status */} + + {/* Overview Section */} (null); - const [boxSessions, setBoxSessions] = useState([]); useEffect(() => { const fetchPluginSystemStatus = async () => { @@ -465,14 +454,8 @@ function PluginListView() { const handleShowDebugInfo = async () => { try { - const [info, box, sessions] = await Promise.all([ - httpClient.getPluginDebugInfo(), - httpClient.getBoxStatus().catch(() => null), - httpClient.getBoxSessions().catch(() => [] as BoxSessionInfo[]), - ]); + const info = await httpClient.getPluginDebugInfo(); setDebugInfo(info); - setBoxStatus(box); - setBoxSessions(sessions); setDebugPopoverOpen(true); } catch (error) { console.error('Failed to fetch debug info:', error); @@ -662,129 +645,6 @@ function PluginListView() {

)}
- - {/* Box Runtime Status */} -
-
- -

- {t('plugins.boxStatusTitle')} -

-
- {boxStatus ? ( -
-
- - {t('plugins.boxStatus')}: - - {boxStatus.available ? ( - - - {t('plugins.boxConnected')} - - ) : ( - - - {t('plugins.boxUnavailable')} - - )} -
- {boxStatus.backend && ( -
- - {t('plugins.boxBackend')}: - - - {boxStatus.backend.name} - {boxStatus.backend.available - ? '' - : ` (${t('plugins.boxUnavailable')})`} - -
- )} -
- - {t('plugins.boxProfile')}: - - {boxStatus.profile} -
-
- - {t('plugins.boxSandboxes')}: - - - {boxStatus.active_sessions ?? 0} - -
- {(boxStatus.recent_error_count ?? 0) > 0 && ( -
- - {t('plugins.boxErrors')}: - - - {boxStatus.recent_error_count} - -
- )} - {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()} - -
-
- ))} -
- )} -
- ) : ( -

- {t('plugins.boxStatusLoadFailed')} -

- )} -
diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 2f9b32aa..4f792852 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -1253,6 +1253,16 @@ const enUS = { sessions: 'Sessions', feedback: 'User Feedback', }, + pluginRuntime: 'Plugin Runtime', + boxRuntime: 'Box Runtime', + connected: 'Connected', + disconnected: 'Disconnected', + disabled: 'Disabled', + statusDetail: 'Status', + pluginDisabled: 'Plugin system is disabled', + boxBackend: 'Backend', + boxProfile: 'Profile', + boxSandboxes: 'Sandboxes', }, limitation: { maxBotsReached: diff --git a/web/src/i18n/locales/es-ES.ts b/web/src/i18n/locales/es-ES.ts index 6d509237..3f85e3a7 100644 --- a/web/src/i18n/locales/es-ES.ts +++ b/web/src/i18n/locales/es-ES.ts @@ -1279,6 +1279,16 @@ const esES = { sessions: 'Sesiones', feedback: 'Comentarios de usuarios', }, + pluginRuntime: 'Plugin Runtime', + boxRuntime: 'Box Runtime', + connected: 'Conectado', + disconnected: 'Desconectado', + disabled: 'Desactivado', + statusDetail: 'Estado', + pluginDisabled: 'El sistema de plugins está desactivado', + boxBackend: 'Backend', + boxProfile: 'Perfil', + boxSandboxes: 'Sandboxes', }, limitation: { maxBotsReached: diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 38988aba..09bc99fa 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -1251,6 +1251,16 @@ const jaJP = { sessions: 'セッション', feedback: 'ユーザーフィードバック', }, + pluginRuntime: 'プラグインランタイム', + boxRuntime: 'Box ランタイム', + connected: '接続済み', + disconnected: '未接続', + disabled: '無効', + statusDetail: 'ステータス', + pluginDisabled: 'プラグインシステムが無効です', + boxBackend: 'バックエンド', + boxProfile: 'プロファイル', + boxSandboxes: 'サンドボックス', }, limitation: { maxBotsReached: diff --git a/web/src/i18n/locales/ru-RU.ts b/web/src/i18n/locales/ru-RU.ts index 48b32ea5..cc09d83c 100644 --- a/web/src/i18n/locales/ru-RU.ts +++ b/web/src/i18n/locales/ru-RU.ts @@ -1253,6 +1253,16 @@ const ruRU = { sessions: 'Сессии', feedback: 'Отзывы пользователей', }, + pluginRuntime: 'Среда плагинов', + boxRuntime: 'Среда Box', + connected: 'Подключено', + disconnected: 'Отключено', + disabled: 'Отключено', + statusDetail: 'Статус', + pluginDisabled: 'Система плагинов отключена', + boxBackend: 'Бэкенд', + boxProfile: 'Профиль', + boxSandboxes: 'Песочницы', }, limitation: { maxBotsReached: diff --git a/web/src/i18n/locales/th-TH.ts b/web/src/i18n/locales/th-TH.ts index 35573da3..b2693eda 100644 --- a/web/src/i18n/locales/th-TH.ts +++ b/web/src/i18n/locales/th-TH.ts @@ -1226,6 +1226,16 @@ const thTH = { sessions: 'เซสชัน', feedback: 'ความคิดเห็นผู้ใช้', }, + pluginRuntime: 'Plugin Runtime', + boxRuntime: 'Box Runtime', + connected: 'เชื่อมต่อแล้ว', + disconnected: 'ไม่ได้เชื่อมต่อ', + disabled: 'ปิดใช้งาน', + statusDetail: 'สถานะ', + pluginDisabled: 'ระบบปลั๊กอินถูกปิดใช้งาน', + boxBackend: 'แบ็กเอนด์', + boxProfile: 'โปรไฟล์', + boxSandboxes: 'แซนด์บ็อกซ์', }, limitation: { maxBotsReached: diff --git a/web/src/i18n/locales/vi-VN.ts b/web/src/i18n/locales/vi-VN.ts index a7a0c179..64ff7f7f 100644 --- a/web/src/i18n/locales/vi-VN.ts +++ b/web/src/i18n/locales/vi-VN.ts @@ -1247,6 +1247,16 @@ const viVN = { sessions: 'Phiên', feedback: 'Phản hồi người dùng', }, + pluginRuntime: 'Plugin Runtime', + boxRuntime: 'Box Runtime', + connected: 'Đã kết nối', + disconnected: 'Chưa kết nối', + disabled: 'Đã tắt', + statusDetail: 'Trạng thái', + pluginDisabled: 'Hệ thống plugin đã tắt', + boxBackend: 'Backend', + boxProfile: 'Hồ sơ', + boxSandboxes: 'Sandbox', }, limitation: { maxBotsReached: diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index c0fa2b97..ed1f3040 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -1199,6 +1199,16 @@ const zhHans = { sessions: '会话记录', feedback: '用户反馈', }, + pluginRuntime: '插件运行时', + boxRuntime: 'Box 运行时', + connected: '已连接', + disconnected: '未连接', + disabled: '已禁用', + statusDetail: '状态', + pluginDisabled: '插件系统已禁用', + boxBackend: '后端', + boxProfile: '配置', + boxSandboxes: '沙箱数', }, limitation: { maxBotsReached: diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index 128dee4f..d9ff4518 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -1192,6 +1192,16 @@ const zhHant = { sessions: '會話記錄', feedback: '使用者回饋', }, + pluginRuntime: '外掛執行時', + boxRuntime: 'Box 執行時', + connected: '已連線', + disconnected: '未連線', + disabled: '已停用', + statusDetail: '狀態', + pluginDisabled: '外掛系統已停用', + boxBackend: '後端', + boxProfile: '設定檔', + boxSandboxes: '沙箱數', }, limitation: { maxBotsReached: