Add project-scoped engine connectivity remediation to the dashboard. - Add engine status/start API helpers and server routes that can resume paused projects or start missing engines. - Render a project banner with dashboard-only guidance, polling, disabled starting state, localized copy, and focused tests. - Document the engine status banner behavior and add a changeset for the published CLI package. Files changed: .changeset/fn-6827-engine-disconnected-banner.md | 7 + docs/dashboard-guide.md | 8 + packages/dashboard/app/api/legacy.ts | 20 +++ .../app/components/EngineStatusBanner.css | 87 +++++++++++ .../app/components/EngineStatusBanner.tsx | 62 ++++++++ .../__tests__/EngineStatusBanner.test.tsx | 145 +++++++++++++++++ .../app/components/dashboard/DashboardBanners.tsx | 3 + .../app/hooks/__tests__/useEngineStatus.test.ts | 171 +++++++++++++++++++++ packages/dashboard/app/hooks/useEngineStatus.ts | 111 +++++++++++++ packages/dashboard/src/__tests__/server.test.ts | 129 ++++++++++++++++ packages/dashboard/src/server.ts | 89 +++++++++++ packages/i18n/locales/en/app.json | 10 ++ packages/i18n/locales/es/app.json | 10 ++ packages/i18n/locales/fr/app.json | 10 ++ packages/i18n/locales/ko/app.json | 10 ++ packages/i18n/locales/zh-CN/app.json | 10 ++ packages/i18n/locales/zh-TW/app.json | 10 ++ 17 files changed, 892 insertions(+) Fusion-Task-Id: FN-6827 Fusion-Task-Lineage: 56821ca3-04c8-4674-9400-3639f10e463d
63 lines
3.0 KiB
TypeScript
63 lines
3.0 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { Loader2 } from "lucide-react";
|
|
import { useEngineStatus } from "../hooks/useEngineStatus";
|
|
import "./EngineStatusBanner.css";
|
|
|
|
interface EngineStatusBannerProps {
|
|
projectId: string;
|
|
}
|
|
|
|
/*
|
|
* FNXC:EngineStatusBanner 2026-06-22-00:00:
|
|
* The project banner stack needs a single visible remediation when the dashboard is loaded but the current project's engine is absent. Hide the entire component once `connected` is true so no empty wrapper, button shell, or stale aria surface remains in the DOM.
|
|
*/
|
|
export function EngineStatusBanner({ projectId }: EngineStatusBannerProps) {
|
|
const { t } = useTranslation("app");
|
|
const { status, canStart, starting, error, start } = useEngineStatus(projectId);
|
|
|
|
if (!status || status.connected) return null;
|
|
|
|
const isDashboardOnly = status.reason === "dashboard-only" || status.reason === "unreachable";
|
|
const statusDotClass = starting ? "status-dot status-dot--connecting" : "status-dot status-dot--error";
|
|
const body = canStart
|
|
? t("engineBanner.body", "The engine for this project is not running, so task automation and live updates may be paused. Start it now to reconnect the dashboard.")
|
|
: t("engineBanner.dashboardOnly", "This dashboard cannot start engines from the current process. Run `fn serve` for this project to enable task execution and live automation.");
|
|
|
|
return (
|
|
<section className="engine-status-banner" role="status" aria-live="polite" data-testid="engine-status-banner">
|
|
<div className="engine-status-banner__indicator" aria-hidden="true">
|
|
<span className={statusDotClass} />
|
|
</div>
|
|
<div className="engine-status-banner__content">
|
|
<div className="engine-status-banner__title">{t("engineBanner.title", "Project engine is not connected")}</div>
|
|
<p className="engine-status-banner__body">
|
|
{isDashboardOnly ? (
|
|
<>
|
|
{t("engineBanner.dashboardOnlyPrefix", "This dashboard cannot start engines from the current process. Run")} <code>fn serve</code> {t("engineBanner.dashboardOnlySuffix", "for this project to enable task execution and live automation.")}
|
|
</>
|
|
) : body}
|
|
</p>
|
|
{error && (
|
|
<p className="engine-status-banner__error" role="alert">
|
|
{t("engineBanner.error", "Start failed: {{message}}", { message: error })}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="engine-status-banner__actions">
|
|
{canStart ? (
|
|
<button
|
|
type="button"
|
|
className="btn btn-primary btn-sm engine-status-banner__start"
|
|
onClick={() => void start()}
|
|
disabled={starting}
|
|
data-testid="engine-status-start-button"
|
|
>
|
|
{starting ? <Loader2 className="spinner" aria-hidden="true" /> : null}
|
|
{starting ? t("engineBanner.starting", "Starting…") : t("engineBanner.startCta", "Start engine")}
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|