mAi 7331f334a8 merge: frame resize handle (bottom-right corner)
Mirrors picasso's device-resize pattern. 10x10 handle at frame's
bottom-right; pointerdown→drag→PATCH width+height on release. Min
200x150. Doesn't interfere with frame-body drag or label-as-grip
selection.
2026-05-17 17:21:52 +02:00

CableGUI

Cable-management framework for m's setup — visual web editor backed by a single Go binary + SQLite, generating Excalidraw drawings via mExDraw.

Each cable-managed environment (LOFT, OFFICE, …) is a separate CableGUI project; each project is backed by exactly one .excalidraw drawing on mxdrw.msbls.de.

Status

Slice 1 — bootstrap shipped. Projects + global cable types are end-to-end; the SVG canvas is intentionally empty until slice 2.

Slice What's in it Status
1 Project CRUD, global cable types, empty SVG canvas, project picker
2 Frames + devices, drag-to-position pending
3 Ports + cables (click-port → click-port) pending
4 IO markers + cable-type editing pending
5 Export to mxdrw.msbls.de pending

Run it

go run ./cmd/cablegui
# open http://localhost:7777

Or built:

make build
./bin/cablegui

The binary serves the frontend from an embedded web/static/ and the JSON API under /api/. SQLite lives at ./data/cablegui.db by default.

Environment

Var Default Notes
CABLEGUI_ADDR 0.0.0.0:7777 Listen address.
CABLEGUI_DB ./data/cablegui.db SQLite path. Parent dir is created on boot.
MEXDRAW_BASE_URL https://mxdrw.msbls.de Base URL for mExDraw export.
MEXDRAW_USER (unset) Username for the mxdrw HTTP Basic Auth on export. Required.
MEXDRAW_PASS (unset) Password for the mxdrw HTTP Basic Auth on export. Required.

Tests

make test           # go test -race ./...

Store-level tests cover projects + cable-types CRUD, the drawing_name auto-default, the ?confirm=<name> guardrail on DELETE /api/projects/:pid, and the ON DELETE RESTRICT on a referenced cable type.

API (slice 1)

GET    /api/healthz                       → 200 {"status":"ok"}

GET    /api/projects                      → [Project, …]
POST   /api/projects                      ← {name, drawing_name?, description?}
                                            drawing_name defaults to "<name>.excalidraw"
GET    /api/projects/:pid                 → {project, cable_types, frames, devices, …}
PATCH  /api/projects/:pid                 ← partial
DELETE /api/projects/:pid?confirm=<name>  ← confirm must equal current name

GET    /api/cable-types                   → [CableType, …]   (global)
POST   /api/cable-types                   ← {name, color}
PATCH  /api/cable-types/:id               ← partial — affects every project
DELETE /api/cable-types/:id               ← 409 in_use if any cable references it

Deploy to mDock

CableGUI runs on mDock at http://mdock:7777 as a docker-compose service under /home/m/stacks/cablegui/. Pattern matches the other mDock services (mgreen-journal, mgeo, msports-garmin, …) — no Dokploy, no reverse proxy, LAN-trusted.

Manual deploy

  1. Build + push the image (image now lives under m/ in Gitea):

    docker build -t mgit.msbls.de/m/cablegui:latest .
    awk '/machine mgit.msbls.de/{getline; getline; print $2}' ~/.netrc \
      | docker login mgit.msbls.de -u m --password-stdin
    docker push mgit.msbls.de/m/cablegui:latest
    
  2. Prepare directories on mDock (one-time):

    ssh mdock 'mkdir -p /home/m/stacks/cablegui/data /home/m/secrets/cablegui \
               && touch /home/m/secrets/cablegui/.env \
               && chmod 0600 /home/m/secrets/cablegui/.env'
    scp docker-compose.yml mdock:/home/m/stacks/cablegui/docker-compose.yml
    
  3. Pull + start:

    ssh mdock 'cd /home/m/stacks/cablegui && docker compose pull && docker compose up -d'
    
  4. Verify from any LAN host:

    curl http://mdock:7777/api/healthz       # → {"status":"ok"}
    curl http://mdock:7777/api/cable-types   # → the 5 seeded types
    

To update to a new build: rebuild + push the image, then ssh mdock 'cd /home/m/stacks/cablegui && docker compose pull && docker compose up -d'.

Persistence

SQLite lives at /home/m/stacks/cablegui/data/cablegui.db on the host (bind-mounted into the container at /app/data). Container runs as UID 1000:1000 to align with m:m ownership on mDock — DB files end up owned by m, the host user.

docker compose restart keeps the data intact (tested 2026-05-15).

Automation — follow-up task

This first roll is manual. A Gitea Actions workflow on the self-hosted runner already on mDock (/home/m/act-runner/, label self-hosted:host) — build → push → docker compose up -d on every push to main — is a separate task per the design's §10. Tracking spawned by the head if/when wanted.

Design + project conventions

  • docs/design.md — full v3 design (schema, API, importer/export conventions, slices, mDock deploy notes).
  • CLAUDE.md — project instructions for mai workers.

Architecture

Layer Tech
DB SQLite via modernc.org/sqlite (cgo-free), WAL, FKs on
Backend Go 1.22+ net/http ServeMux pattern routing, single binary
Frontend Vanilla ES modules + SVG, no build step, embedded via embed.FS
Export (slice 5) mExDraw HTTP API on mxdrw.msbls.de

LAN-trusted, no auth.

Description
Cable management — visual interface + SQLite inventory, integrates with mExDraw for diagrams.
Readme 378 KiB
Languages
Go 58.5%
JavaScript 34.7%
CSS 4.1%
HTML 2.4%
Dockerfile 0.2%