mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-24 21:36:06 +00:00
feat(web): surface Box disabled/unavailable state across consumers
When Box is disabled in config (``box.enabled = false``) or fails to connect, every dependent UI surface now degrades visibly: - ``useBoxStatus`` hook: shared, polled 30s, exposes ``available``, ``disabled`` (config-off) and a single ``hint`` key so callers don't have to re-derive the three states - ``BoxUnavailableNotice`` reusable Alert banner driven by that hint - Dashboard SystemStatusCards: three-state dot + label (connected / disabled-gray / disconnected-red); disabled state shows the ``boxDisabled`` hint, failed state continues to show the connector error. Plugin block kept untouched - Skills page (create view) and SkillDetailContent (edit view): Save button disabled and banner inserted above the form when Box is unavailable — matches the backend gate added in the previous commit - PipelineExtension skill section: ``enable_all_skills`` switch, Add Skill button and Remove buttons all gate on Box availability; banner inline under the section header - PipelineFormComponent: banner above the ``local-agent`` stage card when Box is unavailable, since that stage carries the sandbox-bound ``box-session-id-template`` field - Box status payload type (``ApiRespBoxStatus.enabled``) and 8 locale files updated with ``boxDisabled`` / ``boxUnavailable`` / ``boxRequiredHint`` strings Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,14 +36,16 @@ import {
|
||||
} from '@/components/ui/tooltip';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
|
||||
function StatusDot({ ok }: { ok: boolean | null }) {
|
||||
if (ok === null)
|
||||
type StatusState = 'ok' | 'disabled' | 'failed' | null;
|
||||
|
||||
function StatusDot({ state }: { state: StatusState }) {
|
||||
if (state === null)
|
||||
return <span className="w-2 h-2 rounded-full bg-muted-foreground/40" />;
|
||||
return ok ? (
|
||||
<span className="w-2 h-2 rounded-full bg-green-500" />
|
||||
) : (
|
||||
<span className="w-2 h-2 rounded-full bg-red-500" />
|
||||
);
|
||||
if (state === 'ok')
|
||||
return <span className="w-2 h-2 rounded-full bg-green-500" />;
|
||||
if (state === 'disabled')
|
||||
return <span className="w-2 h-2 rounded-full bg-muted-foreground/60" />;
|
||||
return <span className="w-2 h-2 rounded-full bg-red-500" />;
|
||||
}
|
||||
|
||||
interface SystemStatusCardProps {
|
||||
@@ -86,7 +88,25 @@ export default function SystemStatusCard({
|
||||
const pluginOk = pluginStatus
|
||||
? pluginStatus.is_enable && pluginStatus.is_connected
|
||||
: null;
|
||||
const pluginState: StatusState = pluginStatus
|
||||
? pluginStatus.is_enable && pluginStatus.is_connected
|
||||
? 'ok'
|
||||
: !pluginStatus.is_enable
|
||||
? 'disabled'
|
||||
: 'failed'
|
||||
: null;
|
||||
const boxOk = boxStatus ? boxStatus.available : null;
|
||||
// Box has three observable states: connected (ok), disabled by config
|
||||
// (enabled = false → distinct gray dot + "disabled" hint), and configured
|
||||
// but failed (red dot + connector_error). The dashboard must distinguish
|
||||
// them so operators can tell intentional-off from misconfigured.
|
||||
const boxState: StatusState = boxStatus
|
||||
? boxStatus.available
|
||||
? 'ok'
|
||||
: boxStatus.enabled === false
|
||||
? 'disabled'
|
||||
: 'failed'
|
||||
: null;
|
||||
|
||||
const handleOpenDialog = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
@@ -129,12 +149,12 @@ export default function SystemStatusCard({
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<StatusDot ok={pluginOk} />
|
||||
<StatusDot state={pluginState} />
|
||||
<Plug className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
<span className="text-sm">{t('monitoring.pluginRuntime')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<StatusDot ok={boxOk} />
|
||||
<StatusDot state={boxState} />
|
||||
<Box className="w-3.5 h-3.5 text-muted-foreground" />
|
||||
<span className="text-sm">{t('monitoring.boxRuntime')}</span>
|
||||
</div>
|
||||
@@ -207,24 +227,39 @@ export default function SystemStatusCard({
|
||||
</div>
|
||||
<div className="ml-6 text-sm space-y-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{boxOk ? (
|
||||
{boxState === 'ok' ? (
|
||||
<CircleCheck className="w-4 h-4 text-green-600" />
|
||||
) : (
|
||||
<CircleX className="w-4 h-4 text-red-500" />
|
||||
<CircleX
|
||||
className={
|
||||
boxState === 'disabled'
|
||||
? 'w-4 h-4 text-muted-foreground'
|
||||
: 'w-4 h-4 text-red-500'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={
|
||||
boxOk
|
||||
boxState === 'ok'
|
||||
? 'text-green-600 font-medium'
|
||||
: 'text-red-500 font-medium'
|
||||
: boxState === 'disabled'
|
||||
? 'text-muted-foreground font-medium'
|
||||
: 'text-red-500 font-medium'
|
||||
}
|
||||
>
|
||||
{boxOk
|
||||
{boxState === 'ok'
|
||||
? t('monitoring.connected')
|
||||
: t('monitoring.disconnected')}
|
||||
: boxState === 'disabled'
|
||||
? t('monitoring.disabled')
|
||||
: t('monitoring.disconnected')}
|
||||
</span>
|
||||
</div>
|
||||
{boxStatus && !boxOk && boxStatus.connector_error && (
|
||||
{boxState === 'disabled' && (
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t('monitoring.boxDisabled')}
|
||||
</p>
|
||||
)}
|
||||
{boxState === 'failed' && boxStatus?.connector_error && (
|
||||
<p className="text-red-400 text-xs break-all">
|
||||
{boxStatus.connector_error}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user