From 9298838cf5e5fadad7b5fcb864b2b8ded8d7372a Mon Sep 17 00:00:00 2001 From: xmanrui <841206367@qq.com> Date: Tue, 24 Feb 2026 14:54:41 +0800 Subject: [PATCH] feat: add gateway health status indicator on bot dashboard - Add /api/gateway-health endpoint to check gateway service status - Add GatewayStatus component with 10s auto-polling - Show green checkmark when healthy, red cross with error details when down - Click gateway badge to open OpenClaw web chat page - Fix variable name collision in alert-monitor.tsx (setInterval shadowing) - Add i18n translations for gateway status (zh/en) --- app/alert-monitor.tsx | 2 +- app/api/gateway-health/route.ts | 39 +++++++++++++++++++++ app/gateway-status.tsx | 60 +++++++++++++++++++++++++++++++++ app/page.tsx | 6 ++++ lib/i18n.tsx | 10 ++++++ 5 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 app/api/gateway-health/route.ts create mode 100644 app/gateway-status.tsx diff --git a/app/alert-monitor.tsx b/app/alert-monitor.tsx index b5a872b..588d2f1 100644 --- a/app/alert-monitor.tsx +++ b/app/alert-monitor.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from "react"; // 后台告警检查组件 - 服务启动时自动开始 export function AlertMonitor() { const [enabled, setEnabled] = useState(false); - const [interval, setInterval] = useState(10); + const [checkInterval, setCheckInterval] = useState(10); const [lastResults, setLastResults] = useState([]); useEffect(() => { diff --git a/app/api/gateway-health/route.ts b/app/api/gateway-health/route.ts new file mode 100644 index 0000000..e089256 --- /dev/null +++ b/app/api/gateway-health/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; + +const OPENCLAW_HOME = process.env.OPENCLAW_HOME || path.join(process.env.HOME || "", ".openclaw"); +const CONFIG_PATH = path.join(OPENCLAW_HOME, "openclaw.json"); + +export async function GET() { + try { + const raw = fs.readFileSync(CONFIG_PATH, "utf-8"); + const config = JSON.parse(raw); + const port = config.gateway?.port || 18789; + const token = config.gateway?.auth?.token || ""; + + const url = `http://localhost:${port}/api/health`; + const headers: Record = {}; + if (token) headers["Authorization"] = `Bearer ${token}`; + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + + const resp = await fetch(url, { headers, signal: controller.signal }); + clearTimeout(timeout); + + if (!resp.ok) { + return NextResponse.json({ ok: false, error: `HTTP ${resp.status}` }); + } + + const data = await resp.json().catch(() => null); + return NextResponse.json({ ok: true, data, webUrl: `http://localhost:${port}/chat${token ? '?token=' + encodeURIComponent(token) : ''}` }); + } catch (err: any) { + const msg = err.cause?.code === "ECONNREFUSED" + ? "Gateway 未运行" + : err.name === "AbortError" + ? "请求超时" + : err.message; + return NextResponse.json({ ok: false, error: msg }); + } +} diff --git a/app/gateway-status.tsx b/app/gateway-status.tsx new file mode 100644 index 0000000..c9de8be --- /dev/null +++ b/app/gateway-status.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { useI18n } from "@/lib/i18n"; + +interface HealthResult { + ok: boolean; + error?: string; + data?: any; + webUrl?: string; +} + +export function GatewayStatus() { + const { t } = useI18n(); + const [health, setHealth] = useState(null); + const [showError, setShowError] = useState(false); + + const check = useCallback(() => { + fetch("/api/gateway-health") + .then((r) => r.json()) + .then((d) => setHealth(d)) + .catch(() => setHealth({ ok: false, error: t("gateway.fetchError") })); + }, [t]); + + useEffect(() => { + check(); + const timer = setInterval(check, 10000); + return () => clearInterval(timer); + }, [check]); + + if (!health) return null; + + return ( +
+ + 🌐 Gateway + + + {health.ok ? ( + + ) : ( + setShowError((v) => !v)} + >❌ + )} + {showError && !health.ok && health.error && ( +
+ {health.error} +
+ )} +
+ ); +} diff --git a/app/page.tsx b/app/page.tsx index d1f5d8a..cf869c2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from "react"; import { useI18n } from "@/lib/i18n"; +import { GatewayStatus } from "./gateway-status"; interface Platform { name: string; @@ -611,6 +612,11 @@ export default function Home() { + {/* Gateway 状态 */} +
+ +
+ {/* 卡片墙 */}
{data.agents.map((agent) => ( diff --git a/lib/i18n.tsx b/lib/i18n.tsx index 13e358a..b9a0649 100644 --- a/lib/i18n.tsx +++ b/lib/i18n.tsx @@ -184,6 +184,11 @@ const translations: Record> = { "skills.noDesc": "无描述", "skills.source.builtin": "内置", "skills.source.custom": "自定义", + + // gateway status + "gateway.healthy": "Gateway 运行正常", + "gateway.unhealthy": "Gateway 异常", + "gateway.fetchError": "无法检查 Gateway 状态", }, en: { // layout @@ -364,6 +369,11 @@ const translations: Record> = { "skills.noDesc": "No description", "skills.source.builtin": "Built-in", "skills.source.custom": "Custom", + + // gateway status + "gateway.healthy": "Gateway is running", + "gateway.unhealthy": "Gateway is down", + "gateway.fetchError": "Cannot check Gateway status", }, };