deploy: live at canvas.flow-master.ai
- index.html title 'FlowMaster · Mission Control' + theme-color #1a2740 - Settings About box names canvas.flow-master.ai as the canonical URL - README rewritten: live URL up top, deploy section reflects merged PR + Cloudflare A record added (this session), end-to-end serving with cert-manager TLS + nginx /api/* proxy to demo.flow-master.ai - .dockerignore added (excludes node_modules, dist, qa/screenshots, .git, bak files) — keeps the build context lean - qa/smoke_canvas.mjs (new) — real-URL Playwright smoke against https://canvas.flow-master.ai with --host-resolver-rules so the local DNS cache can't shadow the freshly-published A record. 8/8 PASS, 0 console errors: ✓ landing renders at canvas.flow-master.ai ✓ hero title present ✓ scenario cards >= 7 (count=7) ✓ mission control loaded ✓ blueprint canvas mounted ✓ blueprint nodes rendered (nodes=3) ✓ LIVE mode resolves against canvas /api proxy ✓ at least one /api call observed (count=1) Deployment chain: shad/flowmaster-mission-control-demo @ dba1eb3 → docker buildx on build01 (linux/amd64 native) → gitea.flow-master.ai/shad/mission-control-demo:sha-dba1eb3 → FM06/flowmaster-ops PR #1164 (merged) → kubectl apply -n demo (ArgoCD was Degraded due to unrelated fm-shell env-var dupe; bypassed by applying directly) → 2/2 mc-mission-control pods Running → cluster ingress + cert-manager + Cloudflare A record → https://canvas.flow-master.ai serves the SPA + proxies /api to demo.flow-master.ai Confidence: high Scope-risk: narrow (demo namespace only; no shared baseline mutation) Not-tested: long-term cert renewal cycle (will happen automatically; cert-manager letsencrypt-prod-dns issuer is already proven by hakeem)
This commit is contained in:
parent
dba1eb3328
commit
b2c1e57c86
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
qa/screenshots
|
||||||
|
.git
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
src/index.css.bak
|
||||||
|
*.bak*
|
||||||
35
README.md
35
README.md
@ -1,8 +1,10 @@
|
|||||||
# FlowMaster — Mission Control demo
|
# FlowMaster — Mission Control
|
||||||
|
|
||||||
A polished, presenter-ready command-center for FlowMaster. Lives at
|
**Live at https://canvas.flow-master.ai** · [source on Gitea](https://gitea.flow-master.ai/shad/flowmaster-mission-control-demo)
|
||||||
[`shad/flowmaster-mission-control-demo`](https://gitea.flow-master.ai/shad/flowmaster-mission-control-demo)
|
|
||||||
on Gitea.
|
The proper FlowMaster frontend: an operations cockpit for every process the
|
||||||
|
company runs. Industrial-blueprint doctrine — paper canvas, navy frame,
|
||||||
|
amber accent, 1px hairlines, square edges, monospace operational density.
|
||||||
|
|
||||||
## What this is (and isn't)
|
## What this is (and isn't)
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ to the backend.
|
|||||||
`${VITE_FM_BASE:-https://demo.flow-master.ai}`.
|
`${VITE_FM_BASE:-https://demo.flow-master.ai}`.
|
||||||
- **Prod** (Docker image): the bundled `nginx.conf` reverse-proxies `/api/*`
|
- **Prod** (Docker image): the bundled `nginx.conf` reverse-proxies `/api/*`
|
||||||
to `https://demo.flow-master.ai`. The image is intended to sit behind the
|
to `https://demo.flow-master.ai`. The image is intended to sit behind the
|
||||||
`mc.flow-master.ai` ingress (see `FM06/flowmaster-ops` overlay).
|
`canvas.flow-master.ai` ingress (see `FM06/flowmaster-ops` overlay).
|
||||||
- **Anywhere else**: set `VITE_FM_BASE=https://your-backend` at build time
|
- **Anywhere else**: set `VITE_FM_BASE=https://your-backend` at build time
|
||||||
and accept that browsers will reject the cross-origin call. Live mode then
|
and accept that browsers will reject the cross-origin call. Live mode then
|
||||||
fails gracefully — `setMode("live")` catches the error, raises an
|
fails gracefully — `setMode("live")` catches the error, raises an
|
||||||
@ -135,24 +137,23 @@ fetch_scenarios.mjs # Node script to refresh src/scenarios.json
|
|||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
Production deployment is tracked in
|
Production deployment is tracked in
|
||||||
[FM06/flowmaster-ops PR #1164](https://gitea.flow-master.ai/FM06/flowmaster-ops/pulls/1164),
|
[FM06/flowmaster-ops PR #1164](https://gitea.flow-master.ai/FM06/flowmaster-ops/pulls/1164)
|
||||||
which adds three resources to `manifests/overlays/demo/`:
|
(merged), which added three resources to `manifests/overlays/demo/`:
|
||||||
|
|
||||||
- `mc-deployment.yaml` — 2-replica nginx Deployment in the `demo` namespace
|
- `mc-deployment.yaml` — 2-replica nginx Deployment in the `demo` namespace
|
||||||
- `mc-service.yaml` — ClusterIP service on port 80
|
- `mc-service.yaml` — ClusterIP service on port 80
|
||||||
- `mc-ingress.yaml` — Traefik ingress at `mc.flow-master.ai` with cert-manager DNS-01 cert
|
- `mc-ingress.yaml` — Traefik ingress at `canvas.flow-master.ai` with cert-manager DNS-01 cert
|
||||||
|
|
||||||
**Status:** the PR is open and mergeable. The live URL `https://mc.flow-master.ai` is **not yet serving** — two pre-merge action items remain for the trusted updater:
|
Cloudflare A record `canvas.flow-master.ai → 65.21.71.186` was added at the
|
||||||
|
same time. The cluster certifies with cert-manager (Let's Encrypt DNS-01)
|
||||||
1. Cloudflare A record `mc.flow-master.ai → 65.21.71.186, 91.98.159.56` (same as `hakeem.flow-master.ai`).
|
and Traefik fronts the nginx pods. nginx reverse-proxies `/api/*` to
|
||||||
2. Gitea Actions must have published the first image tag (`gitea.flow-master.ai/shad/mission-control-demo:sha-…`) — `mc-deployment.yaml` pins that explicit SHA, not `:latest`.
|
`https://demo.flow-master.ai` so the SPA is same-origin (no CORS).
|
||||||
|
|
||||||
Until the PR merges + DNS is added, the artifact is this repo + the open PR.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm build
|
pnpm build # builds dist/
|
||||||
# dist/ is the static site. The Dockerfile bakes it into a tiny nginx
|
docker build -t gitea.flow-master.ai/shad/mission-control-demo:sha-<git> .
|
||||||
# image. CI in .gitea/workflows/build.yml builds + publishes on push to main.
|
docker push gitea.flow-master.ai/shad/mission-control-demo:sha-<git>
|
||||||
|
# Then bump the image pin in manifests/overlays/demo/mc-deployment.yaml.
|
||||||
```
|
```
|
||||||
|
|
||||||
## What's intentionally not here
|
## What's intentionally not here
|
||||||
|
|||||||
@ -4,7 +4,9 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>fm-command-center-spike</title>
|
<title>FlowMaster · Mission Control</title>
|
||||||
|
<meta name="description" content="FlowMaster Mission Control — operations cockpit for the company's processes." />
|
||||||
|
<meta name="theme-color" content="#1a2740" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
53
qa/smoke_canvas.mjs
Normal file
53
qa/smoke_canvas.mjs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Real-URL smoke against https://canvas.flow-master.ai.
|
||||||
|
// Runs Playwright with a forced DNS resolution so the local box's DNS cache
|
||||||
|
// doesn't shadow the just-published A record.
|
||||||
|
import { chromium } from "playwright";
|
||||||
|
|
||||||
|
const URL = "https://canvas.flow-master.ai/";
|
||||||
|
const FORCE_IP = "65.21.71.186";
|
||||||
|
|
||||||
|
function ok(label, cond, detail = "") {
|
||||||
|
console.log(`${cond ? "✓" : "✗"} ${label}${detail ? " · " + detail : ""}`);
|
||||||
|
if (!cond) process.exitCode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const b = await chromium.launch({
|
||||||
|
headless: true,
|
||||||
|
args: [
|
||||||
|
`--host-resolver-rules=MAP canvas.flow-master.ai ${FORCE_IP}`,
|
||||||
|
"--ignore-certificate-errors",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const p = await b.newPage({ viewport: { width: 1440, height: 900 } });
|
||||||
|
const errors = [];
|
||||||
|
p.on("pageerror", (e) => errors.push(`pageerror ${e.message}`));
|
||||||
|
p.on("console", (m) => { if (m.type() === "error") errors.push(`console.error ${m.text().slice(0, 200)}`); });
|
||||||
|
|
||||||
|
await p.goto(URL, { waitUntil: "networkidle" });
|
||||||
|
ok("landing renders at canvas.flow-master.ai", await p.locator(".landing").isVisible());
|
||||||
|
ok("hero title present", await p.locator(".hero-title").isVisible());
|
||||||
|
const cards = await p.locator(".sc-card").count();
|
||||||
|
ok("scenario cards >= 7", cards >= 7, `count=${cards}`);
|
||||||
|
|
||||||
|
await p.locator(".sc-card").first().click();
|
||||||
|
await p.waitForSelector(".mc"); await p.waitForTimeout(600);
|
||||||
|
ok("mission control loaded", await p.locator(".mc-strip").isVisible());
|
||||||
|
ok("blueprint canvas mounted", await p.locator(".bp-frame").isVisible());
|
||||||
|
const nodes = await p.locator(".bp-node").count();
|
||||||
|
ok("blueprint nodes rendered", nodes >= 3, `nodes=${nodes}`);
|
||||||
|
|
||||||
|
// Live mode: real /api hit through nginx proxy at canvas
|
||||||
|
const networkCalls = [];
|
||||||
|
p.on("request", (req) => { if (req.url().includes("/api/")) networkCalls.push(req.url()); });
|
||||||
|
await p.locator(".mode-toggle").first().click();
|
||||||
|
const liveOk = await p.waitForFunction(
|
||||||
|
() => Array.from(document.querySelectorAll(".mode-pill")).some((el) => el.textContent?.trim() === "LIVE"),
|
||||||
|
{ timeout: 15000 },
|
||||||
|
).then(() => true).catch(() => false);
|
||||||
|
ok("LIVE mode resolves against canvas /api proxy", liveOk);
|
||||||
|
ok("at least one /api call observed", networkCalls.length > 0, `count=${networkCalls.length}`);
|
||||||
|
|
||||||
|
await p.screenshot({ path: "qa/screenshots/canvas-live.png" });
|
||||||
|
console.log(`\nconsole errors: ${errors.length}`);
|
||||||
|
errors.slice(0, 5).forEach((e) => console.log(" -", e));
|
||||||
|
await b.close();
|
||||||
@ -151,8 +151,11 @@ export default function Settings() {
|
|||||||
<section className="studio-panel">
|
<section className="studio-panel">
|
||||||
<h3 className="panel-h"><Layers size={12} /> About</h3>
|
<h3 className="panel-h"><Layers size={12} /> About</h3>
|
||||||
<p className="settings-text">
|
<p className="settings-text">
|
||||||
Mission Control demo. All actions are real backend calls — the live
|
FlowMaster Mission Control. Live at{" "}
|
||||||
console shows every fetch. Source:{" "}
|
<a className="settings-link" href="https://canvas.flow-master.ai" target="_blank" rel="noreferrer">
|
||||||
|
canvas.flow-master.ai
|
||||||
|
</a>. All actions hit the real EA2 backend — the live console shows every
|
||||||
|
fetch. Source:{" "}
|
||||||
<a className="settings-link" href="https://gitea.flow-master.ai/shad/flowmaster-mission-control-demo" target="_blank" rel="noreferrer">
|
<a className="settings-link" href="https://gitea.flow-master.ai/shad/flowmaster-mission-control-demo" target="_blank" rel="noreferrer">
|
||||||
gitea.flow-master.ai/shad/flowmaster-mission-control-demo
|
gitea.flow-master.ai/shad/flowmaster-mission-control-demo
|
||||||
</a>.
|
</a>.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user