fix: Dockerfile npm run build + htmx for layout switch buttons

- Dockerfile.server: RUN npm run build during builder stage so the
  image ships pre-compiled lib/ + bsb-plugin.json. Runtime image also
  installs ffmpeg (for camera snapshot endpoint).
- DisplayEditPage Show buttons + Switch dropdown now use hx-post
  with hx-swap=none — no page reload, just fires the command.
This commit is contained in:
Mitchell R 2026-05-13 01:32:25 +02:00
parent 5669222f48
commit 766db445c4
2 changed files with 30 additions and 16 deletions

View file

@ -3,7 +3,7 @@ FROM node:23-bookworm-slim AS builder
WORKDIR /app
# Install build deps for argon2
# Build deps for argon2 + bsb-plugin-cli
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential python3 \
&& rm -rf /var/lib/apt/lists/*
@ -11,15 +11,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
COPY package.json package-lock.json ./
COPY server/package.json ./server/
COPY tsconfig.base.json ./
RUN npm ci --ignore-scripts && npm rebuild argon2
RUN npm ci && npm rebuild argon2
COPY server ./server
# Run BSB build — extracts schemas + compiles TS + generates plugin manifests
WORKDIR /app/server
RUN npm run build
# ---- Runtime image ----
FROM node:23-bookworm-slim
# ffmpeg for camera snapshot capture (optional but needed for /admin/entities/:id/snapshot)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
ca-certificates ffmpeg \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -d /var/lib/betterframe -s /bin/false betterframe
@ -31,7 +36,6 @@ COPY --from=builder /app/server ./server
COPY --from=builder /app/tsconfig.base.json ./
COPY --from=builder /app/package.json ./
# Default data dir
RUN mkdir -p /var/lib/betterframe && chown betterframe:betterframe /var/lib/betterframe
VOLUME /var/lib/betterframe

View file

@ -2043,19 +2043,23 @@ export function DisplayEditPage(props: DisplayEditPageProps) {
{props.attachedLayouts.length > 0 && d.kiosk_id ? (
<div style="margin-bottom:1rem; padding:0.75rem; background:#f9fafb; border-radius:4px">
<div style="font-size:0.85rem; font-weight:600; margin-bottom:0.5rem">Switch Layout Now</div>
<form
method="post"
action={`/admin/displays/${d.id}/layout/0`}
style="display:flex; gap:0.5rem; align-items:center"
{...{ "onsubmit": "this.action = this.action.replace(/\\/layout\\/.*/, '/layout/' + this.layout_id.value); return true;" }}
>
<select name="layout_id" class="form-input" style="flex:1">
<div style="display:flex; gap:0.5rem; align-items:center">
<select id={`layout-pick-${d.id}`} class="form-input" style="flex:1">
{props.attachedLayouts.map((l) => (
<option value={String(l.id)}>{l.name}</option>
))}
</select>
<button type="submit" class="btn btn-sm">Switch</button>
</form>
<button
type="button"
class="btn btn-sm"
{...{
"hx-post": `/admin/displays/${d.id}/layout/0`,
"hx-swap": "none",
"hx-vals": "js:{}",
"hx-on::config-request": `event.detail.path = event.detail.path.replace(/\\/layout\\/.*/, '/layout/' + document.getElementById('layout-pick-${d.id}').value);`,
}}
>Switch</button>
</div>
</div>
) : null}
@ -2125,9 +2129,15 @@ export function DisplayEditPage(props: DisplayEditPageProps) {
<td><span class={`badge ${l.priority === "hot" ? "badge-red" : l.priority === "cold" ? "badge-blue" : "badge-gray"}`}>{l.priority}</span></td>
<td>{d.default_layout_id === l.id ? <span class="badge badge-green">Yes</span> : ""}</td>
<td>
<form method="post" action={`/admin/displays/${d.id}/layout/${l.id}`} style="display:inline; margin-right:0.25rem">
<button type="submit" class="btn btn-sm">Show</button>
</form>
<button
type="button"
class="btn btn-sm"
style="margin-right:0.25rem"
{...{
"hx-post": `/admin/displays/${d.id}/layout/${l.id}`,
"hx-swap": "none",
}}
>Show</button>
<form method="post" action={`/admin/displays/${d.id}/layouts/${l.id}/remove`} style="display:inline">
<button type="submit" class="btn btn-sm btn-danger" {...{"onclick": "return confirm('Detach this layout from the display?')"}}>Detach</button>
</form>