50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { TriangleAlert } from "lucide-react";
|
||
import { api, type HealthResult } from "@/components/api";
|
||
|
||
const INSTALL_HINTS: Record<string, string> = {
|
||
sling: "brew install slingdata-io/sling/sling(或参考 https://docs.slingdata.io)",
|
||
atlas: "brew install ariga/tap/atlas(或参考 https://atlasgo.io)",
|
||
};
|
||
|
||
export function HealthBanner() {
|
||
const [health, setHealth] = useState<HealthResult | null>(null);
|
||
const [failed, setFailed] = useState(false);
|
||
|
||
useEffect(() => {
|
||
api<HealthResult>("/api/health")
|
||
.then(setHealth)
|
||
.catch(() => setFailed(true));
|
||
}, []);
|
||
|
||
if (!health) {
|
||
if (!failed) return null;
|
||
return (
|
||
<div className="border-b border-amber-500/40 bg-amber-500/10 px-4 py-2 text-sm text-amber-700 dark:text-amber-400">
|
||
无法获取 CLI 健康状态(/api/health 请求失败)。
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const missing = (["sling", "atlas"] as const).filter((k) => !health[k]);
|
||
if (missing.length === 0) return null;
|
||
|
||
return (
|
||
<div className="flex items-start gap-2 border-b border-amber-500/40 bg-amber-500/10 px-4 py-2 text-sm text-amber-700 dark:text-amber-400">
|
||
<TriangleAlert className="mt-0.5 size-4 shrink-0" />
|
||
<div className="flex flex-col gap-0.5">
|
||
{missing.map((k) => (
|
||
<span key={k}>
|
||
未检测到 <span className="font-mono font-medium">{k}</span> CLI
|
||
{k === "atlas" ? "(schema 同步不可用)" : "(数据迁移不可用)"}
|
||
,安装提示:
|
||
<span className="font-mono">{INSTALL_HINTS[k]}</span>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|