diff --git a/apps/web/app.vue b/apps/web/app.vue index d9673c2..1ddf06a 100644 --- a/apps/web/app.vue +++ b/apps/web/app.vue @@ -25,6 +25,10 @@ onBeforeUnmount(() => {
Continuum + {{ telemetry.link === "connected" @@ -84,4 +88,20 @@ body { .app-shell__main { padding: 1.25rem; } + +.app-shell__nav { + display: flex; + gap: 0.75rem; +} + +.app-shell__nav a { + color: inherit; + opacity: 0.7; + text-decoration: none; +} + +.app-shell__nav a.router-link-exact-active { + opacity: 1; + text-decoration: underline; +} diff --git a/apps/web/composables/useApi.ts b/apps/web/composables/useApi.ts index ff2a476..a416504 100644 --- a/apps/web/composables/useApi.ts +++ b/apps/web/composables/useApi.ts @@ -41,3 +41,13 @@ export function useApiFetch() { }); }; } + +/** + * A sentence for a failed API call. The backend's `{ error: { message } }` + * body is what explains a 403 (a viewer cannot do this), a 409 or a 502, so + * it is surfaced instead of a bare "fetch failed". + */ +export function apiErrorMessage(err: unknown, fallback: string): string { + const body = (err as { data?: { error?: { message?: string } } }).data; + return body?.error?.message ?? fallback; +} diff --git a/apps/web/pages/index.vue b/apps/web/pages/index.vue index 6395f30..fe3507c 100644 --- a/apps/web/pages/index.vue +++ b/apps/web/pages/index.vue @@ -58,8 +58,7 @@ async function sendCommand(printerId: string, command: PrinterCommand): Promise< try { await apiFetch(`/printers/${printerId}/commands`, { method: "POST", body: command }); } catch (err) { - const body = (err as { data?: { error?: { message?: string } } }).data; - throw new Error(body?.error?.message ?? "the command could not be sent"); + throw new Error(apiErrorMessage(err, "the command could not be sent")); } } diff --git a/apps/web/pages/jobs.vue b/apps/web/pages/jobs.vue new file mode 100644 index 0000000..afc9592 --- /dev/null +++ b/apps/web/pages/jobs.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/packages/ui/src/components/JobList.vue b/packages/ui/src/components/JobList.vue new file mode 100644 index 0000000..bd2fbf8 --- /dev/null +++ b/packages/ui/src/components/JobList.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/packages/ui/src/components/JobUpload.vue b/packages/ui/src/components/JobUpload.vue new file mode 100644 index 0000000..3f3c18c --- /dev/null +++ b/packages/ui/src/components/JobUpload.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 0ab3fab..2278115 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -2,4 +2,6 @@ export { default as PrinterCard } from "./components/PrinterCard.vue"; export { default as PrinterControls } from "./components/PrinterControls.vue"; export { default as TemperatureGraph } from "./components/TemperatureGraph.vue"; export { default as WebRTCStreamView } from "./components/WebRTCStreamView.vue"; -export type { AppPrinterState, AppTelemetryMessage, PrinterCommand } from "./types"; +export { default as JobList } from "./components/JobList.vue"; +export { default as JobUpload } from "./components/JobUpload.vue"; +export type { AppFarm, AppJob, AppPrinterState, AppTelemetryMessage, PrinterCommand } from "./types"; diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index c0a33fe..7257fb3 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -58,3 +58,24 @@ export type AppTelemetryMessage = export type PrinterCommand = | { action: "control_print"; control: "pause" | "resume" | "stop" } | { action: "set_fan_speed"; percent: number }; + +/** A farm as `GET /farms` returns it (the fields the console uses). */ +export interface AppFarm { + id: string; + name: string; +} + +/** + * A print job as `GET /jobs` returns it. `status` is the backend's + * `print_job_status` enum but typed open, like `AppPrinterState.status`: an + * unknown value renders in the default colour rather than breaking. + */ +export interface AppJob { + id: string; + farmId: string; + printerId?: string | null; + fileName: string; + status: string; + progressPct?: number | null; + createdAt: string; +}