From b2c1e57c86f30ae6d0b8460a628052cee73713f9 Mon Sep 17 00:00:00 2001 From: Shad Date: Sun, 14 Jun 2026 02:46:34 +0400 Subject: [PATCH] deploy: live at canvas.flow-master.ai MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .dockerignore | 10 ++++++++ README.md | 35 ++++++++++++++------------- index.html | 4 +++- qa/smoke_canvas.mjs | 53 +++++++++++++++++++++++++++++++++++++++++ src/scenes/Settings.tsx | 7 ++++-- 5 files changed, 89 insertions(+), 20 deletions(-) create mode 100644 .dockerignore create mode 100644 qa/smoke_canvas.mjs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3b0c16d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +node_modules +dist +qa/screenshots +.git +*.log +.DS_Store +.vscode +.idea +src/index.css.bak +*.bak* diff --git a/README.md b/README.md index e798d2b..e152443 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# FlowMaster — Mission Control demo +# FlowMaster — Mission Control -A polished, presenter-ready command-center for FlowMaster. Lives at -[`shad/flowmaster-mission-control-demo`](https://gitea.flow-master.ai/shad/flowmaster-mission-control-demo) -on Gitea. +**Live at https://canvas.flow-master.ai** · [source on Gitea](https://gitea.flow-master.ai/shad/flowmaster-mission-control-demo) + +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) @@ -77,7 +79,7 @@ to the backend. `${VITE_FM_BASE:-https://demo.flow-master.ai}`. - **Prod** (Docker image): the bundled `nginx.conf` reverse-proxies `/api/*` 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 and accept that browsers will reject the cross-origin call. Live mode then fails gracefully — `setMode("live")` catches the error, raises an @@ -135,24 +137,23 @@ fetch_scenarios.mjs # Node script to refresh src/scenarios.json ## Deploy Production deployment is tracked in -[FM06/flowmaster-ops PR #1164](https://gitea.flow-master.ai/FM06/flowmaster-ops/pulls/1164), -which adds three resources to `manifests/overlays/demo/`: +[FM06/flowmaster-ops PR #1164](https://gitea.flow-master.ai/FM06/flowmaster-ops/pulls/1164) +(merged), which added three resources to `manifests/overlays/demo/`: - `mc-deployment.yaml` — 2-replica nginx Deployment in the `demo` namespace - `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: - -1. Cloudflare A record `mc.flow-master.ai → 65.21.71.186, 91.98.159.56` (same as `hakeem.flow-master.ai`). -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`. - -Until the PR merges + DNS is added, the artifact is this repo + the open PR. +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) +and Traefik fronts the nginx pods. nginx reverse-proxies `/api/*` to +`https://demo.flow-master.ai` so the SPA is same-origin (no CORS). ```bash -pnpm build -# dist/ is the static site. The Dockerfile bakes it into a tiny nginx -# image. CI in .gitea/workflows/build.yml builds + publishes on push to main. +pnpm build # builds dist/ +docker build -t gitea.flow-master.ai/shad/mission-control-demo:sha- . +docker push gitea.flow-master.ai/shad/mission-control-demo:sha- +# Then bump the image pin in manifests/overlays/demo/mc-deployment.yaml. ``` ## What's intentionally not here diff --git a/index.html b/index.html index a4ba5e4..375974c 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,9 @@ - fm-command-center-spike + FlowMaster · Mission Control + +
diff --git a/qa/smoke_canvas.mjs b/qa/smoke_canvas.mjs new file mode 100644 index 0000000..9fbe594 --- /dev/null +++ b/qa/smoke_canvas.mjs @@ -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(); diff --git a/src/scenes/Settings.tsx b/src/scenes/Settings.tsx index ef9045c..712b91e 100644 --- a/src/scenes/Settings.tsx +++ b/src/scenes/Settings.tsx @@ -151,8 +151,11 @@ export default function Settings() {

About

- Mission Control demo. All actions are real backend calls — the live - console shows every fetch. Source:{" "} + FlowMaster Mission Control. Live at{" "} + + canvas.flow-master.ai + . All actions hit the real EA2 backend — the live console shows every + fetch. Source:{" "} gitea.flow-master.ai/shad/flowmaster-mission-control-demo .