From 9bd9728b21fe441e570792e63b7d4d83ee80aed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iwo=20Strzebo=C5=84ski?= Date: Thu, 29 Oct 2026 15:15:06 +0000 Subject: [PATCH] Add Setup: first farm, gateway settings, add and remove printers; show alerts A new user could sign in and see an empty page with no way forward. Setup walks them through it: create a farm (reusing their organisation if they already have one); get the gateway settings with a freshly minted token, shown once and removed from the page when dismissed, with a confirmation first if a gateway is already connected, since minting replaces the old token; add a Bambu printer by address, serial and access code (a printer the gateway discovered fills the form in); remove a printer behind a named confirmation that says it also deletes the stored credential. The access code field is cleared after every submit, success or failure, and the page says plainly that the code goes to the gateway and nowhere else. Add is disabled while no gateway is connected, because the code has nowhere to go. The fleet page now lists open alerts above the printers (polled every 5s) with a Dismiss button, and points an empty account at Setup. Verified in real Chrome by scripts/e2e-setup-browser.sh in continuum-backend: nothing -> farm -> gateway -> printer -> failed-print alert -> dismissed -> removed, with no page errors. --- apps/web/app.vue | 1 + apps/web/pages/index.vue | 29 +++- apps/web/pages/setup.vue | 128 +++++++++++++++++ packages/ui/src/components/AddPrinter.vue | 135 ++++++++++++++++++ packages/ui/src/components/AlertList.vue | 84 +++++++++++ packages/ui/src/components/GatewaySetup.vue | 115 +++++++++++++++ .../ui/src/components/ManagedPrinters.vue | 70 +++++++++ packages/ui/src/components/OnboardingForm.vue | 54 +++++++ packages/ui/src/index.ts | 7 +- packages/ui/src/types.ts | 29 ++++ 10 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 apps/web/pages/setup.vue create mode 100644 packages/ui/src/components/AddPrinter.vue create mode 100644 packages/ui/src/components/AlertList.vue create mode 100644 packages/ui/src/components/GatewaySetup.vue create mode 100644 packages/ui/src/components/ManagedPrinters.vue create mode 100644 packages/ui/src/components/OnboardingForm.vue diff --git a/apps/web/app.vue b/apps/web/app.vue index 1ddf06a..12f5a0f 100644 --- a/apps/web/app.vue +++ b/apps/web/app.vue @@ -28,6 +28,7 @@ onBeforeUnmount(() => { {{ diff --git a/apps/web/pages/index.vue b/apps/web/pages/index.vue index 4c851c2..18c4bfc 100644 --- a/apps/web/pages/index.vue +++ b/apps/web/pages/index.vue @@ -1,6 +1,8 @@ + + + + diff --git a/packages/ui/src/components/AddPrinter.vue b/packages/ui/src/components/AddPrinter.vue new file mode 100644 index 0000000..0189f8b --- /dev/null +++ b/packages/ui/src/components/AddPrinter.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/packages/ui/src/components/AlertList.vue b/packages/ui/src/components/AlertList.vue new file mode 100644 index 0000000..e57519f --- /dev/null +++ b/packages/ui/src/components/AlertList.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/packages/ui/src/components/GatewaySetup.vue b/packages/ui/src/components/GatewaySetup.vue new file mode 100644 index 0000000..41de89b --- /dev/null +++ b/packages/ui/src/components/GatewaySetup.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/packages/ui/src/components/ManagedPrinters.vue b/packages/ui/src/components/ManagedPrinters.vue new file mode 100644 index 0000000..b6c011e --- /dev/null +++ b/packages/ui/src/components/ManagedPrinters.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/packages/ui/src/components/OnboardingForm.vue b/packages/ui/src/components/OnboardingForm.vue new file mode 100644 index 0000000..994623e --- /dev/null +++ b/packages/ui/src/components/OnboardingForm.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 955be66..4c0b069 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -4,5 +4,10 @@ export { default as TemperatureGraph } from "./components/TemperatureGraph.vue"; export { default as WebRTCStreamView } from "./components/WebRTCStreamView.vue"; export { default as JobList } from "./components/JobList.vue"; export { default as JobUpload } from "./components/JobUpload.vue"; +export { default as AlertList } from "./components/AlertList.vue"; +export { default as OnboardingForm } from "./components/OnboardingForm.vue"; +export { default as GatewaySetup } from "./components/GatewaySetup.vue"; +export { default as AddPrinter } from "./components/AddPrinter.vue"; +export { default as ManagedPrinters } from "./components/ManagedPrinters.vue"; export { default as FarmStatus } from "./components/FarmStatus.vue"; -export type { AppDiscoveredCandidate, AppFarm, AppFarmDiscovery, AppJob, AppPrinterState, AppTelemetryMessage, PrinterCommand } from "./types"; +export type { NewPrinter, AppOrg, AppAlert, AppDiscoveredCandidate, AppFarm, AppFarmDiscovery, AppJob, AppPrinterState, AppTelemetryMessage, PrinterCommand } from "./types"; diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index c3bc1ac..1b51692 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -101,3 +101,32 @@ export interface AppFarmDiscovery { gatewayOnline: boolean; discovery: { scannedAt: string; printers: AppDiscoveredCandidate[] } | null; } + +/** + * `GET /alerts`. `kind` is a plain string for the same reason `AppJob.status` + * is: an unknown one should render, not break the page. + */ +export interface AppAlert { + id: string; + farmId: string; + printerId?: string | null; + kind: string; + message: string; + confidence?: number | null; + acknowledgedAt?: string | null; + createdAt: string; +} + +/** `GET /orgs`: the organisations the caller belongs to. */ +export interface AppOrg { + id: string; + name: string; +} + +/** What the add-printer form hands to the page. The access code is a secret: send it, do not keep it. */ +export interface NewPrinter { + name: string; + host: string; + serialNumber: string; + accessCode: string; +}