From 65de42d4959ff96b732147204fdb60266ae8cc78 Mon Sep 17 00:00:00 2001 From: Mitchell R Date: Wed, 27 May 2026 02:09:40 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20AbleSign=20UI=20=E2=80=94=20single?= =?UTF-8?q?=20account,=20screen=20detail,=20no=20kiosk=20assign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove Accounts from AbleSign nav (one account per tenant) - Screens page: create button, no kiosk assignment - Screen detail page with config form - Internal/External badge Co-Authored-By: Claude Opus 4.6 (1M context) --- .../service-admin-http/routes-ablesign.ts | 79 +++++++++--- server/src/web-templates/admin-pages.tsx | 115 ++++++++++++------ server/src/web-templates/layout.tsx | 1 - 3 files changed, 140 insertions(+), 55 deletions(-) diff --git a/server/src/plugins/service-admin-http/routes-ablesign.ts b/server/src/plugins/service-admin-http/routes-ablesign.ts index 908722b..92f7d00 100644 --- a/server/src/plugins/service-admin-http/routes-ablesign.ts +++ b/server/src/plugins/service-admin-http/routes-ablesign.ts @@ -6,7 +6,7 @@ import { type H3, getRouterParam, readBody, createError } from "h3"; import { htmlPage } from "./html-response.js"; import type { AdminDeps } from "./index.js"; import * as ablesign from "../../shared/ablesign.js"; -import { AbleSignPage, AbleSignScreensPage, AbleSignContentPage, AbleSignPlaylistsPage } from "../../web-templates/admin-pages.js"; +import { AbleSignPage, AbleSignScreensPage, AbleSignScreenDetailPage, AbleSignContentPage, AbleSignPlaylistsPage } from "../../web-templates/admin-pages.js"; export function registerAbleSignRoutes(app: H3, deps: AdminDeps): void { @@ -55,19 +55,12 @@ export function registerAbleSignRoutes(app: H3, deps: AdminDeps): void { }); } catch { /* sync failure is non-fatal */ } - return new Response(null, { status: 302, headers: { location: `/admin/ablesign/${accountId}/screens` } }); + return new Response(null, { status: 302, headers: { location: "/admin/ablesign/screens" } }); }); - app.get("/admin/ablesign/:id/screens", async (event) => { - const id = getRouterParam(event, "id") ?? ""; - const account = await deps.repo.getAbleSignAccount(id); - if (!account) throw createError({ statusCode: 404, statusMessage: "Account not found" }); - const screens = await deps.repo.listAbleSignScreens(id); - const kiosks = await deps.repo.listKiosks(); - for (const s of screens) { - (s as any).has_entity = !!(await deps.repo.getEntityByAbleSignScreen(s.id)); - } - return htmlPage(AbleSignScreensPage({ account, screens, kiosks })); + // Redirect old per-account route to global screens page. + app.get("/admin/ablesign/:id/screens", async () => { + return new Response(null, { status: 302, headers: { location: "/admin/ablesign/screens" } }); }); app.post("/admin/ablesign/:id/sync", async (event) => { @@ -114,7 +107,7 @@ export function registerAbleSignRoutes(app: H3, deps: AdminDeps): void { const body = await readBody>(event); const title = (body?.title ?? "").trim(); if (!title) { - return new Response(null, { status: 302, headers: { location: `/admin/ablesign/${accountId}/screens` } }); + return new Response(null, { status: 302, headers: { location: "/admin/ablesign/screens" } }); } try { @@ -155,7 +148,7 @@ export function registerAbleSignRoutes(app: H3, deps: AdminDeps): void { // redirect back — error handling TODO } - return new Response(null, { status: 302, headers: { location: `/admin/ablesign/${accountId}/screens` } }); + return new Response(null, { status: 302, headers: { location: "/admin/ablesign/screens" } }); }); app.post("/admin/ablesign/screens/:sid/assign", async (event) => { @@ -166,7 +159,55 @@ export function registerAbleSignRoutes(app: H3, deps: AdminDeps): void { const screen = await deps.repo.getAbleSignScreen(sid); const accountId = screen?.account_id ?? ""; - return new Response(null, { status: 302, headers: { location: `/admin/ablesign/${accountId}/screens` } }); + return new Response(null, { status: 302, headers: { location: "/admin/ablesign/screens" } }); + }); + + // ---- Screen detail + config ------------------------------------------------- + + app.get("/admin/ablesign/screens/:sid", async (event) => { + const sid = getRouterParam(event, "sid") ?? ""; + const screen = await deps.repo.getAbleSignScreen(sid); + if (!screen) throw createError({ statusCode: 404, statusMessage: "Screen not found" }); + const account = await deps.repo.getAbleSignAccount(screen.account_id); + let remoteScreen: any = null; + if (account) { + try { + const apiKey = deps.secrets.decryptString(account.api_key_encrypted, "ablesign-key"); + remoteScreen = await ablesign.getScreen( + { apiKey, workspaceId: account.workspace_id || undefined }, + Number(screen.ablesign_screen_id), + ); + } catch { /* remote fetch failed */ } + } + const entity = await deps.repo.getEntityByAbleSignScreen(sid); + return htmlPage(AbleSignScreenDetailPage({ screen, remoteScreen, entity })); + }); + + app.post("/admin/ablesign/screens/:sid", async (event) => { + const sid = getRouterParam(event, "sid") ?? ""; + const screen = await deps.repo.getAbleSignScreen(sid); + if (!screen) throw createError({ statusCode: 404, statusMessage: "Screen not found" }); + const account = await deps.repo.getAbleSignAccount(screen.account_id); + if (!account) throw createError({ statusCode: 404, statusMessage: "Account not found" }); + + const body = await readBody>(event); + const title = (body?.title ?? "").trim(); + const orientation = body?.orientation ?? "landscape"; + const description = (body?.description ?? "").trim(); + + try { + const apiKey = deps.secrets.decryptString(account.api_key_encrypted, "ablesign-key"); + await ablesign.updateScreen( + { apiKey, workspaceId: account.workspace_id || undefined }, + Number(screen.ablesign_screen_id), + { title: title || undefined, orientation, description: description || undefined }, + ); + if (title) { + await deps.repo.updateAbleSignScreen(sid, { title, orientation }); + } + } catch { /* update failed */ } + + return new Response(null, { status: 302, headers: { location: `/admin/ablesign/screens/${sid}` } }); }); app.post("/admin/ablesign/:id/delete", async (event) => { @@ -192,19 +233,19 @@ export function registerAbleSignRoutes(app: H3, deps: AdminDeps): void { await deps.repo.deleteAbleSignScreen(sid); } const accountId = screen?.account_id ?? ""; - return new Response(null, { status: 302, headers: { location: `/admin/ablesign/${accountId}/screens` } }); + return new Response(null, { status: 302, headers: { location: "/admin/ablesign/screens" } }); }); // ---- Global views (all accounts aggregated) -------------------------------- app.get("/admin/ablesign/screens", async () => { - const screens = await deps.repo.listAbleSignScreens(); - const kiosks = await deps.repo.listKiosks(); const accounts = await deps.repo.listAbleSignAccounts(); + const account = accounts[0] ?? null; + const screens = account ? await deps.repo.listAbleSignScreens(account.id) : []; for (const s of screens) { (s as any).has_entity = !!(await deps.repo.getEntityByAbleSignScreen(s.id)); } - return htmlPage(AbleSignScreensPage({ account: null, screens, kiosks, accounts })); + return htmlPage(AbleSignScreensPage({ screens, accountId: account?.id ?? null })); }); app.get("/admin/ablesign/content", async () => { diff --git a/server/src/web-templates/admin-pages.tsx b/server/src/web-templates/admin-pages.tsx index a8fa71b..80d431c 100644 --- a/server/src/web-templates/admin-pages.tsx +++ b/server/src/web-templates/admin-pages.tsx @@ -4453,51 +4453,51 @@ export function AbleSignPage(props: AbleSignPageProps) { } interface AbleSignScreensPageProps { - account: any | null; screens: any[]; - kiosks: any[]; - accounts?: any[]; + accountId: string | null; + error?: string; } export function AbleSignScreensPage(props: AbleSignScreensPageProps) { - const a = props.account; - const isGlobal = !a; - const title = isGlobal ? "AbleSign — All Screens" : `AbleSign — ${String(a.name)}`; + const aid = props.accountId; return ( - -

{isGlobal ? "All AbleSign Screens" : `${String(a.name)} — Screens`}

- {a ? ( -

- {String(a.screen_count ?? 0)} screens - {a.last_sync_at ? ` · synced ${formatTime(a.last_sync_at)}` : ""} -

- ) : ""} + +

AbleSign Screens

- {a ? ( + {props.error ?
{props.error}
: ""} + + {!aid ? (
-

Add Screen

-
+

No AbleSign account configured. Add one under Account settings first.

+
+ ) : ( +
+

Create Screen

+ +

+ Registers a new screen in AbleSign headlessly and creates a linked entity for use in layouts. +

- ) : ""} + )}

Screens

- {a ? ( -
+ {aid ? ( +
) : ""}
{props.screens.length === 0 ? ( -

No screens yet. Add one above or sync from AbleSign.

+

No screens yet. Create one above or sync from AbleSign.

) : (
@@ -4506,13 +4506,12 @@ export function AbleSignScreensPage(props: AbleSignScreensPageProps) { - {props.screens.map((s: any) => ( - + -
Orientation Status SourceAssigned Kiosk Actions
{s.title}{s.title} {s.orientation} {s.online @@ -4524,18 +4523,6 @@ export function AbleSignScreensPage(props: AbleSignScreensPageProps) { ? Internal : External} -
- - -
-
@@ -4552,6 +4539,64 @@ export function AbleSignScreensPage(props: AbleSignScreensPageProps) { ); } +// ---- AbleSign Screen Detail Page --------------------------------------------- + +interface AbleSignScreenDetailPageProps { + screen: any; + remoteScreen: any | null; + entity: any | null; +} + +export function AbleSignScreenDetailPage(props: AbleSignScreenDetailPageProps) { + const s = props.screen; + const r = props.remoteScreen; + return ( + +

{s.title}

+ +
+

Screen Configuration

+ +
+ + + +
+ + +
+ +
+

Status

+
+
{"AbleSign ID: "}{String(s.ablesign_screen_id)}
+
{"Status: "}{s.online ? "Online" : "Offline"}
+
{"Source: "}{props.entity ? "Internal" : "External"}
+ {props.entity ? : ""} + {r?.heartbeatTime ?
{"Last heartbeat: "}{formatTime(r.heartbeatTime)}
: ""} + {r?.timezone ?
{"Timezone: "}{String(r.timezone)}
: ""} +
+
+ + +
+ ); +} + interface AbleSignContentPageProps { content: any[]; accounts: any[]; } export function AbleSignContentPage(props: AbleSignContentPageProps) { diff --git a/server/src/web-templates/layout.tsx b/server/src/web-templates/layout.tsx index a9c0c57..a25b8a3 100644 --- a/server/src/web-templates/layout.tsx +++ b/server/src/web-templates/layout.tsx @@ -73,7 +73,6 @@ function Sidebar(props: { activeNav?: string }) { -