Compare commits
1 Commits
mai/picass
...
mai/picass
| Author | SHA1 | Date | |
|---|---|---|---|
| 78b37f442d |
14
Dockerfile
14
Dockerfile
@@ -20,17 +20,25 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-o /out/mcables \
|
||||
./cmd/mcables
|
||||
|
||||
# Pre-create the runtime data dir with the right owner in the builder
|
||||
# stage, then COPY it into the distroless final image. Distroless has
|
||||
# no shell + no mkdir, so this is the canonical pattern for "writable
|
||||
# subdir under a non-root user".
|
||||
RUN mkdir -p /out/data && chown -R 1000:1000 /out/data
|
||||
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
WORKDIR /app
|
||||
COPY --from=build /out/mcables /app/mcables
|
||||
COPY --from=build --chown=1000:1000 /out/data /app/data
|
||||
|
||||
ENV MCABLES_ADDR=0.0.0.0:7777 \
|
||||
MCABLES_DB=/app/data/mcables.db
|
||||
|
||||
EXPOSE 7777
|
||||
# Run as UID:GID 1000:1000 to match m on mDock — the bind-mounted
|
||||
# /home/m/stacks/mcables/data is owned by m:m, so the container can write
|
||||
# to it without chowning the host dir. distroless/static-debian12 accepts
|
||||
# arbitrary numeric UIDs; the Go binary doesn't need a /etc/passwd entry.
|
||||
# /home/m/stacks/mcables/data is owned by m:m, so the container can
|
||||
# write to it without chowning the host dir. distroless/static-debian12
|
||||
# accepts arbitrary numeric UIDs; the Go binary doesn't need a
|
||||
# /etc/passwd entry.
|
||||
USER 1000:1000
|
||||
ENTRYPOINT ["/app/mcables"]
|
||||
|
||||
@@ -46,26 +46,7 @@ func New(store *db.Store, frontend fs.FS) http.Handler {
|
||||
mux.HandleFunc("DELETE /api/projects/{pid}/devices/{id}", h.deleteDevice)
|
||||
|
||||
// Frontend (embedded). Serve "/" → index.html via http.FileServerFS.
|
||||
// Wrap in noCache so the browser revalidates with the ETag/Last-Modified
|
||||
// the file server already emits — without this, browsers cache aggressively
|
||||
// and m sees the old main.js after every redeploy until hard-reload.
|
||||
mux.Handle("/", noCache(http.FileServerFS(frontend)))
|
||||
mux.Handle("/", http.FileServerFS(frontend))
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
// noCache wraps a static handler so each response carries
|
||||
// Cache-Control: no-cache. Combined with the ETag/Last-Modified headers
|
||||
// http.FileServer(FS) already emits, this turns every fetch into a
|
||||
// cheap revalidation request — the browser uses its cached body when
|
||||
// the ETag matches but always asks first, so freshly-built assets show
|
||||
// up on the next page load without a hard-reload.
|
||||
//
|
||||
// Applied to the static-asset handler only — API responses write their
|
||||
// own headers and aren't routed through this.
|
||||
func noCache(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -458,13 +458,12 @@ let rubberStart = /** @type {{x:number,y:number}|null} */ (null);
|
||||
|
||||
function onCanvasPointerDown(e) {
|
||||
if (!state.active) return;
|
||||
// Ignore clicks that started on a device/frame — their own handlers
|
||||
// captured the pointer already.
|
||||
if (e.target instanceof Element && e.target.closest("[data-device-id], [data-frame-id]")) return;
|
||||
|
||||
const p = svgPoint(e);
|
||||
|
||||
// Armed tool wins: a click anywhere on the canvas — including on top
|
||||
// of an existing frame or device — fires the tool. The +Dev tool needs
|
||||
// this so m can drop a device inside a frame; without it the frame's
|
||||
// own pointerdown handler would steal the click and start a drag.
|
||||
if (state.tool === "frame") {
|
||||
startFrameRubberBand(e, p);
|
||||
return;
|
||||
@@ -473,11 +472,6 @@ function onCanvasPointerDown(e) {
|
||||
placeDeviceAt(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// No tool armed: clicks that started on a device/frame go to their
|
||||
// own handlers (drag / select). Leave them alone.
|
||||
if (e.target instanceof Element && e.target.closest("[data-device-id], [data-frame-id]")) return;
|
||||
|
||||
// Plain canvas click = clear selection.
|
||||
if (state.selection) { state.selection = null; render(); }
|
||||
}
|
||||
|
||||
@@ -203,15 +203,9 @@ body {
|
||||
.svg-draggable { cursor: grab; }
|
||||
.svg-draggable.dragging { cursor: grabbing; }
|
||||
|
||||
/* Tool cursor while a tool is armed. The `* { ... !important }` descendant
|
||||
rule is the load-bearing part: without it, the `.svg-draggable` rules
|
||||
on individual frame/device rects win by element specificity and
|
||||
override the SVG-root cursor — so hovering a frame with +Dev armed
|
||||
shows `grab`, which lies about what a click will do. */
|
||||
/* tool cursor on the empty canvas while a tool is armed */
|
||||
.canvas-wrap.tool-frame #canvas,
|
||||
.canvas-wrap.tool-frame #canvas *,
|
||||
.canvas-wrap.tool-device #canvas,
|
||||
.canvas-wrap.tool-device #canvas * { cursor: crosshair !important; }
|
||||
.canvas-wrap.tool-device #canvas { cursor: crosshair; }
|
||||
|
||||
.rubber-band {
|
||||
fill: rgba(25, 113, 194, 0.08);
|
||||
|
||||
Reference in New Issue
Block a user