feat(ai): F1-5b Wave A admin-web sandbox 透出 UI-1/2/4 (W1)
完成 F1-5b Wave A admin-web 改造: UI-1 AIRunLogs 列表加 runtime_mode + sandbox_instance_id 列 - 后端 schema RunLogItem 补 runtime_mode / sandbox_instance_id 字段 - 后端 SQL list_run_logs SELECT 加这两列 - 前端 columns 加"运行模式"(orange/blue Tag) + "沙箱实例"(短哈希 + tooltip) UI-2 AIRunLogs 详情 Drawer 加 runtime 字段 - 后端 SQL get_run_log SELECT 加 runtime 列 - 前端 Descriptions 加"运行模式" + "沙箱实例"两项 UI-4 全局 sandbox 徽章(覆盖所有 admin-web 页面) - App.tsx Footer 三段式: 左 sandbox 徽章 / 中 任务状态 / 右 占位 - 30s 轮询 fetchRuntimeContext(userSiteId) - sandbox: 橙色"沙箱"+ 业务日 + 短哈希实例 ID(monospace) - live: 绿色"实时"+ 真实今天 双口径 4a/4b 验证(MCP Playwright 实地走查): - UI-1 4a live: 列表全行 live 蓝 Tag - UI-1 4b sandbox: SQL INSERT walkthrough_ui12 → 列表显示 sandbox 橙 Tag + 短哈希 - UI-2 4b: Drawer 详情 runtime_mode='sandbox' 橙 Tag + sandbox_instance_id monospace 全 ID - UI-4 4a: footer 左侧绿"实时"+ 2026-05-05 - UI-4 4b: 切 sandbox=2026-04-20 后 footer 显示橙"沙箱"+ 业务日 + sbx_e7a7e5c5... - 截图归档 docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/ 剩余 Wave A: MP-3/5 小程序 sandbox / MP-1 board-finance 字段复核 / BE-1 task-list 403 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@@ -32,6 +32,8 @@ import { useAuthStore } from "./store/authStore";
|
||||
import { useBusinessDayStore } from "./store/businessDayStore";
|
||||
import { fetchQueue } from "./api/execution";
|
||||
import type { QueuedTask } from "./types";
|
||||
// F1-5b UI-4: 全局 sandbox 徽章
|
||||
import { fetchRuntimeContext, type RuntimeContext } from "./api/runtimeContext";
|
||||
import Login from "./pages/Login";
|
||||
import EnvConfig from "./pages/EnvConfig";
|
||||
import DBViewer from "./pages/DBViewer";
|
||||
@@ -150,8 +152,11 @@ const AppLayout: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const logout = useAuthStore((s) => s.logout);
|
||||
const userSiteId = useAuthStore((s) => s.user?.site_id);
|
||||
|
||||
const [runningTask, setRunningTask] = useState<QueuedTask | null>(null);
|
||||
// F1-5b UI-4: 当前 site 的 runtime context(sandbox 徽章渲染依据)
|
||||
const [runtimeCtx, setRuntimeCtx] = useState<RuntimeContext | null>(null);
|
||||
|
||||
const pollQueue = useCallback(async () => {
|
||||
try {
|
||||
@@ -163,12 +168,29 @@ const AppLayout: React.FC = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// F1-5b UI-4: 拉 runtime context(30 秒轮询,sandbox 切换不需即时但 5 分钟内必须更新)
|
||||
const pollRuntimeCtx = useCallback(async () => {
|
||||
if (!userSiteId) return;
|
||||
try {
|
||||
const ctx = await fetchRuntimeContext(userSiteId);
|
||||
setRuntimeCtx(ctx);
|
||||
} catch {
|
||||
// 失败不阻塞顶栏渲染
|
||||
}
|
||||
}, [userSiteId]);
|
||||
|
||||
useEffect(() => {
|
||||
pollQueue();
|
||||
const timer = setInterval(pollQueue, 5_000);
|
||||
return () => clearInterval(timer);
|
||||
}, [pollQueue]);
|
||||
|
||||
useEffect(() => {
|
||||
pollRuntimeCtx();
|
||||
const timer = setInterval(pollRuntimeCtx, 30_000);
|
||||
return () => clearInterval(timer);
|
||||
}, [pollRuntimeCtx]);
|
||||
|
||||
const onMenuClick: MenuProps["onClick"] = ({ key }) => { navigate(key); };
|
||||
|
||||
const handleLogout = () => {
|
||||
@@ -266,12 +288,37 @@ const AppLayout: React.FC = () => {
|
||||
</Content>
|
||||
<Footer
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "6px 16px",
|
||||
background: "#fafafa",
|
||||
borderTop: "1px solid #f0f0f0",
|
||||
}}
|
||||
>
|
||||
{/* F1-5b UI-4: 左侧 sandbox 徽章 - sandbox 模式始终可见,提醒离开 AIOperations 后仍知道在虚拟时间 */}
|
||||
<div style={{ minWidth: 180, textAlign: "left" }}>
|
||||
{runtimeCtx?.is_sandbox ? (
|
||||
<Space size={6}>
|
||||
<Tag color="orange" style={{ margin: 0 }}>沙箱</Tag>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
业务日 {runtimeCtx.business_date}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 11, fontFamily: "monospace" }}>
|
||||
{runtimeCtx.sandbox_instance_id?.slice(0, 12)}…
|
||||
</Text>
|
||||
</Space>
|
||||
) : runtimeCtx ? (
|
||||
<Space size={6}>
|
||||
<Tag color="green" style={{ margin: 0 }}>实时</Tag>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{runtimeCtx.business_date}
|
||||
</Text>
|
||||
</Space>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, textAlign: "center" }}>
|
||||
{runningTask ? (
|
||||
<Space size={8}>
|
||||
<Spin size="small" />
|
||||
@@ -285,6 +332,9 @@ const AppLayout: React.FC = () => {
|
||||
) : (
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>无任务执行中</Text>
|
||||
)}
|
||||
</div>
|
||||
{/* 右侧占位,保持 Footer 三段式平衡 */}
|
||||
<div style={{ minWidth: 180 }} />
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
||||
@@ -139,6 +139,9 @@ export interface RunLogItem {
|
||||
status: string;
|
||||
site_id: number;
|
||||
created_at: string;
|
||||
// F1-5b UI-1: sandbox 透出
|
||||
runtime_mode?: string | null;
|
||||
sandbox_instance_id?: string | null;
|
||||
}
|
||||
|
||||
export interface RunLogDetailResponse extends RunLogItem {
|
||||
|
||||
@@ -101,6 +101,21 @@ const AIRunLogs: React.FC = () => {
|
||||
title: "状态", dataIndex: "status", key: "status", width: 110,
|
||||
render: (v: string) => <Tag color={STATUS_COLOR[v] ?? "default"}>{v}</Tag>,
|
||||
},
|
||||
{
|
||||
title: "运行模式", dataIndex: "runtime_mode", key: "runtime_mode", width: 100,
|
||||
render: (v: string | null) => {
|
||||
if (!v) return "—";
|
||||
return <Tag color={v === "sandbox" ? "orange" : "blue"}>{v}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "沙箱实例", dataIndex: "sandbox_instance_id", key: "sandbox_instance_id", width: 130,
|
||||
render: (v: string | null) => {
|
||||
if (!v || v === "live") return "—";
|
||||
const short = v.length > 12 ? v.slice(0, 8) + "…" : v;
|
||||
return <span title={v} style={{ fontFamily: "monospace", fontSize: 12 }}>{short}</span>;
|
||||
},
|
||||
},
|
||||
{ title: "时间", dataIndex: "created_at", key: "created_at", width: 170, render: fmtTime },
|
||||
];
|
||||
|
||||
@@ -192,6 +207,20 @@ const AIRunLogs: React.FC = () => {
|
||||
<Tag color={STATUS_COLOR[detail.status] ?? "default"}>{detail.status}</Tag>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Session ID">{detail.session_id ?? "—"}</Descriptions.Item>
|
||||
<Descriptions.Item label="运行模式">
|
||||
{detail.runtime_mode ? (
|
||||
<Tag color={detail.runtime_mode === "sandbox" ? "orange" : "blue"}>
|
||||
{detail.runtime_mode}
|
||||
</Tag>
|
||||
) : "—"}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="沙箱实例">
|
||||
{detail.sandbox_instance_id && detail.sandbox_instance_id !== "live" ? (
|
||||
<span style={{ fontFamily: "monospace", fontSize: 12 }}>
|
||||
{detail.sandbox_instance_id}
|
||||
</span>
|
||||
) : "—"}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间" span={2}>{fmtTime(detail.created_at)}</Descriptions.Item>
|
||||
<Descriptions.Item label="完成时间" span={2}>{fmtTime(detail.finished_at)}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
@@ -112,7 +112,7 @@ class RetryResponse(BaseModel):
|
||||
|
||||
|
||||
class RunLogItem(BaseModel):
|
||||
"""调用记录列表项。"""
|
||||
"""调用记录列表项。F1-5b UI-1: 加 runtime_mode + sandbox_instance_id 透出 sandbox 状态。"""
|
||||
id: int
|
||||
app_type: str
|
||||
trigger_type: str
|
||||
@@ -122,6 +122,8 @@ class RunLogItem(BaseModel):
|
||||
status: str
|
||||
site_id: int
|
||||
created_at: str
|
||||
runtime_mode: str | None = None
|
||||
sandbox_instance_id: str | None = None
|
||||
|
||||
|
||||
class RunLogListResponse(BaseModel):
|
||||
|
||||
@@ -496,7 +496,8 @@ class AdminAIService:
|
||||
cur.execute(
|
||||
f"""
|
||||
SELECT id, app_type, trigger_type, member_id,
|
||||
tokens_used, latency_ms, status, site_id, created_at
|
||||
tokens_used, latency_ms, status, site_id, created_at,
|
||||
runtime_mode, sandbox_instance_id
|
||||
FROM biz.ai_run_logs
|
||||
{where_sql}
|
||||
ORDER BY created_at DESC
|
||||
@@ -527,7 +528,8 @@ class AdminAIService:
|
||||
SELECT id, app_type, trigger_type, member_id,
|
||||
tokens_used, latency_ms, status, site_id,
|
||||
created_at, request_prompt, response_text,
|
||||
error_message, session_id, finished_at
|
||||
error_message, session_id, finished_at,
|
||||
runtime_mode, sandbox_instance_id
|
||||
FROM biz.ai_run_logs
|
||||
WHERE id = %s
|
||||
""",
|
||||
|
||||
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 236 KiB |