- console: GET /api/workflows/{id}, /runs/{run_id}, /runs/{run_id}/diagnosis
(journal trace + sanitized bounded log tails, workflow/run pairing enforced)
- workbench/: dsh 宿主插件(7 个工作流工具、中文系统提示词、失败监控器、
本机回环桥接服务)+ 侧边栏面板客户端包(sidebar.footer.action 与
shell.overlay 追加插槽)+ 降级独立面板 + 一键启动脚本
- adapters/browser: 收敛 looks_like_login_url 到共享层,修复
jd_main_image_collector 对 gyxx_flow.accounts 的越层导入
- tests: 新端点覆盖;replay_policy 断言对齐已迁移的 catalog(repeatable)
75 lines
2.3 KiB
React
75 lines
2.3 KiB
React
/** 失败告警浮层:轮询桥接告警,新失败弹出 toast,可一键发起诊断会话。 */
|
|
|
|
import { useEffect } from 'react'
|
|
import { useSyncExternalStore } from 'react'
|
|
import { getState, subscribe, update, dismissToast, pushToast } from './store.js'
|
|
import { api } from './api.js'
|
|
|
|
function useWorkbench() {
|
|
return useSyncExternalStore(subscribe, getState)
|
|
}
|
|
|
|
export function Toasts() {
|
|
const s = useWorkbench()
|
|
|
|
useEffect(() => {
|
|
let stopped = false
|
|
let timer = null
|
|
const poll = async () => {
|
|
try {
|
|
const payload = await api.alerts()
|
|
const fresh = (payload.alerts ?? []).filter(
|
|
(alert) => !alert.seen && !getState().seenAlertIds[alert.id],
|
|
)
|
|
if (fresh.length) {
|
|
const seen = { ...getState().seenAlertIds }
|
|
for (const alert of fresh) {
|
|
seen[alert.id] = true
|
|
pushToast({ kind: 'failed', text: `工作流「${alert.name}」运行失败`, alert })
|
|
}
|
|
update({ seenAlertIds: seen })
|
|
}
|
|
} catch {
|
|
// 桥接离线时静默
|
|
}
|
|
if (!stopped) timer = setTimeout(poll, 30_000)
|
|
}
|
|
timer = setTimeout(poll, 8_000)
|
|
return () => {
|
|
stopped = true
|
|
if (timer) clearTimeout(timer)
|
|
}
|
|
}, [])
|
|
|
|
if (!s.toasts.length) return null
|
|
return (
|
|
<div className="gyxxwb-toasts">
|
|
{s.toasts.map((toast) => (
|
|
<div key={toast.id} className={`gyxxwb-toast gyxxwb-toast-${toast.kind}`}>
|
|
<span className="gyxxwb-toast-text">{toast.text}</span>
|
|
{toast.alert && (
|
|
<button
|
|
type="button"
|
|
onClick={async () => {
|
|
try {
|
|
await api.ask({ workflow_id: toast.alert.workflow_id, action: 'diagnose' })
|
|
await api.markAlertSeen(toast.alert.id).catch(() => {})
|
|
pushToast({ kind: 'ok', text: '已创建诊断会话,请在左侧会话列表查看' })
|
|
} catch (error) {
|
|
pushToast({ kind: 'error', text: `创建会话失败:${error.message}` })
|
|
}
|
|
dismissToast(toast.id)
|
|
}}
|
|
>
|
|
立即诊断
|
|
</button>
|
|
)}
|
|
<button type="button" title="关闭" onClick={() => dismissToast(toast.id)}>
|
|
✕
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|