rename: mCables → CableGUI (project + repo + image + paths)
Full project rename per m's call. Single atomic commit because the codebase rename is a coupled change — go module path, env vars, DB default, Docker artefact names, and on-disk mDock paths all flip together. - go.mod: module mgit.msbls.de/m/mcables → mgit.msbls.de/m/cablegui - cmd/mcables → cmd/cablegui (git mv) - All Go imports rewritten to the new module path - Env vars: MCABLES_ADDR/MCABLES_DB → CABLEGUI_ADDR/CABLEGUI_DB - DB default path: data/mcables.db → data/cablegui.db - Dockerfile + docker-compose.yml: image, container_name, env vars, bind-mount /home/m/stacks/mcables → /home/m/stacks/cablegui, secrets /home/m/secrets/mcables → /home/m/secrets/cablegui - Makefile: bin target + run/build commands point at cmd/cablegui - .gitignore + .dockerignore: /mcables → /cablegui - README, docs/design.md, CLAUDE.md: prose + paths + image name - web/static/index.html: <title> + brand - web/static/main.js + web/web.go: header comment - internal/exporter: Scene.Source "mcables" → "cablegui" - internal/server/export.go: error-detail secrets path - internal/db/migrations/*.sql: header comments (mCables vN → CableGUI vN) Memory group_id kept as "mcables" to preserve existing memory continuity. Documented as historical in CLAUDE.md. go build ./... clean; go test -race ./... green
This commit is contained in:
@@ -15,7 +15,7 @@ data
|
|||||||
|
|
||||||
# Build artefacts
|
# Build artefacts
|
||||||
bin
|
bin
|
||||||
/mcables
|
/cablegui
|
||||||
|
|
||||||
# Editor cruft
|
# Editor cruft
|
||||||
.vscode
|
.vscode
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,7 +8,7 @@ data/*.db-shm
|
|||||||
|
|
||||||
# Build artefacts
|
# Build artefacts
|
||||||
bin/
|
bin/
|
||||||
/mcables
|
/cablegui
|
||||||
|
|
||||||
# Editor
|
# Editor
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
31
CLAUDE.md
31
CLAUDE.md
@@ -1,11 +1,11 @@
|
|||||||
# mCables — Project Instructions
|
# CableGUI — Project Instructions
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Cable-management **framework + solver** for m's setup. m declares his
|
Cable-management **framework + solver** for m's setup. m declares his
|
||||||
**devices** and the **connection requirements** between them ("NAS must
|
**devices** and the **connection requirements** between them ("NAS must
|
||||||
connect to Switch via RJ45"). mCables runs a solver that emits the cable
|
connect to Switch via RJ45"). CableGUI runs a solver that emits the cable
|
||||||
plan + bundle recommendations. mCables is a **schematic**, not a
|
plan + bundle recommendations. CableGUI is a **schematic**, not a
|
||||||
physical-routing tool — cables are straight lines between endpoints; the
|
physical-routing tool — cables are straight lines between endpoints; the
|
||||||
"maximum bundling" objective is satisfied by the endpoint-pair rule
|
"maximum bundling" objective is satisfied by the endpoint-pair rule
|
||||||
(when two or more cables share the same A↔B endpoint pair, group them
|
(when two or more cables share the same A↔B endpoint pair, group them
|
||||||
@@ -13,16 +13,17 @@ into one bundle). The visual editor is still there for tweaking the
|
|||||||
plan, but the solver is the headline.
|
plan, but the solver is the headline.
|
||||||
|
|
||||||
Each cable-managed environment (LOFT, OFFICE, …) is a separate
|
Each cable-managed environment (LOFT, OFFICE, …) is a separate
|
||||||
**mCables project**, and each project is backed by exactly one Excalidraw
|
**CableGUI project**, and each project is backed by exactly one Excalidraw
|
||||||
drawing. The framework provides a visual web interface backed by a Go
|
drawing. The framework provides a visual web interface backed by a Go
|
||||||
HTTP API and SQLite, plus an export pipeline that writes `.excalidraw`
|
HTTP API and SQLite, plus an export pipeline that writes `.excalidraw`
|
||||||
files via mExDraw.
|
files via mExDraw.
|
||||||
|
|
||||||
**Memory group_id:** `mcables`
|
**Memory group_id:** `mcables` (kept historical — all prior memories live
|
||||||
|
under this id; renaming would orphan them)
|
||||||
|
|
||||||
**No CLI.** Frontend-first — every interaction is through the visual
|
**No CLI.** Frontend-first — every interaction is through the visual
|
||||||
interface. The backend serves the UI and the API; there is no
|
interface. The backend serves the UI and the API; there is no
|
||||||
`mcables` shell binary intended for humans.
|
`cablegui` shell binary intended for humans.
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ interface. The backend serves the UI and the API; there is no
|
|||||||
|
|
||||||
| Layer | Tech | Notes |
|
| Layer | Tech | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| DB | SQLite | `./data/mcables.db` (project-local, gitignored). Driver: `modernc.org/sqlite` (cgo-free). |
|
| DB | SQLite | `./data/cablegui.db` (project-local, gitignored). Driver: `modernc.org/sqlite` (cgo-free). |
|
||||||
| Backend | Go | `net/http` HTTP API + static frontend via `embed.FS`. Standard library + minimal deps. Single binary. |
|
| Backend | Go | `net/http` HTTP API + static frontend via `embed.FS`. Standard library + minimal deps. Single binary. |
|
||||||
| Frontend | Vanilla JS modules + SVG, no build step | TypeScript types via JSDoc, optional `tsc --noEmit` in CI. Preact-via-CDN-ESM is the documented fallback if vanilla state gets painful — no build step either way. |
|
| Frontend | Vanilla JS modules + SVG, no build step | TypeScript types via JSDoc, optional `tsc --noEmit` in CI. Preact-via-CDN-ESM is the documented fallback if vanilla state gets painful — no build step either way. |
|
||||||
| Diagram I/O | mExDraw HTTP API | `PUT https://mxdrw.msbls.de/api/drawings/<name>.excalidraw` with `Authorization: Bearer $MEXDRAW_TOKEN`. (The `mcp__mexdraw__*` MCP tools are not currently configured for this project — workers use the raw HTTP API.) |
|
| Diagram I/O | mExDraw HTTP API | `PUT https://mxdrw.msbls.de/api/drawings/<name>.excalidraw` with `Authorization: Bearer $MEXDRAW_TOKEN`. (The `mcp__mexdraw__*` MCP tools are not currently configured for this project — workers use the raw HTTP API.) |
|
||||||
@@ -112,14 +113,14 @@ interface. The backend serves the UI and the API; there is no
|
|||||||
|
|
||||||
## Deployment — mDock, raw docker (NOT Dokploy)
|
## Deployment — mDock, raw docker (NOT Dokploy)
|
||||||
|
|
||||||
mCables runs on **mDock** (`192.168.178.131` on the LAN, Tailscale `mdock`)
|
CableGUI runs on **mDock** (`192.168.178.131` on the LAN, Tailscale `mdock`)
|
||||||
as a **plain docker-compose service**. Dokploy is for public mlake/mRiver
|
as a **plain docker-compose service**. Dokploy is for public mlake/mRiver
|
||||||
stuff; mDock uses raw `docker compose` per the conventions of the existing
|
stuff; mDock uses raw `docker compose` per the conventions of the existing
|
||||||
mDock services (mgreen, mgeo, msports-garmin, paperless, …).
|
mDock services (mgreen, mgeo, msports-garmin, paperless, …).
|
||||||
|
|
||||||
- Repo layout on mDock: `/home/m/stacks/mcables/` with `docker-compose.yml`,
|
- Repo layout on mDock: `/home/m/stacks/cablegui/` with `docker-compose.yml`,
|
||||||
`data/` bind-mount, secrets in `/home/m/secrets/mcables/.env`.
|
`data/` bind-mount, secrets in `/home/m/secrets/cablegui/.env`.
|
||||||
- Image: `mgit.msbls.de/m/mcables:latest` (built and pushed by a Gitea
|
- Image: `mgit.msbls.de/m/cablegui:latest` (built and pushed by a Gitea
|
||||||
Actions workflow on push to `main`, runs on the self-hosted runner on
|
Actions workflow on push to `main`, runs on the self-hosted runner on
|
||||||
mDock with label `self-hosted:host`).
|
mDock with label `self-hosted:host`).
|
||||||
- Port mapping: `7777:7777`, exposed on the LAN — no reverse proxy.
|
- Port mapping: `7777:7777`, exposed on the LAN — no reverse proxy.
|
||||||
@@ -127,12 +128,12 @@ mDock services (mgreen, mgeo, msports-garmin, paperless, …).
|
|||||||
- LAN URL: `http://mdock:7777`.
|
- LAN URL: `http://mdock:7777`.
|
||||||
- No auth — LAN-trusted.
|
- No auth — LAN-trusted.
|
||||||
|
|
||||||
Local dev (no Docker): `go run ./cmd/mcables` against `./data/mcables.db`.
|
Local dev (no Docker): `go run ./cmd/cablegui` against `./data/cablegui.db`.
|
||||||
|
|
||||||
## Seed drawing — visual grammar reference, **not** a runtime importer
|
## Seed drawing — visual grammar reference, **not** a runtime importer
|
||||||
|
|
||||||
`mxdrw.msbls.de/draw/Cable-Management.excalidraw` is **reference material
|
`mxdrw.msbls.de/draw/Cable-Management.excalidraw` is **reference material
|
||||||
only**. mCables does **not** auto-ingest it. m will rebuild LOFT and OFFICE
|
only**. CableGUI does **not** auto-ingest it. m will rebuild LOFT and OFFICE
|
||||||
from scratch inside the tool — the seed exists so the **exporter** mimics
|
from scratch inside the tool — the seed exists so the **exporter** mimics
|
||||||
its visual grammar:
|
its visual grammar:
|
||||||
|
|
||||||
@@ -163,13 +164,13 @@ Legend colours (global, seeded once by migration 001):
|
|||||||
|
|
||||||
## Out of scope (v0)
|
## Out of scope (v0)
|
||||||
|
|
||||||
- Multi-user. mCables is m-only.
|
- Multi-user. CableGUI is m-only.
|
||||||
- Auth / sharing — LAN-trusted on mDock.
|
- Auth / sharing — LAN-trusted on mDock.
|
||||||
- Mobile / responsive — desktop browser only.
|
- Mobile / responsive — desktop browser only.
|
||||||
- Cable inventory beyond visual structure (no length, no purchase history,
|
- Cable inventory beyond visual structure (no length, no purchase history,
|
||||||
no SKU). Strictly visual structure for v0.
|
no SKU). Strictly visual structure for v0.
|
||||||
- Import from `.excalidraw` at runtime. If a one-shot migration is ever
|
- Import from `.excalidraw` at runtime. If a one-shot migration is ever
|
||||||
needed, a separate `mcables-migrate` CLI tool is the right shape, not a
|
needed, a separate `cablegui-migrate` CLI tool is the right shape, not a
|
||||||
hot API endpoint.
|
hot API endpoint.
|
||||||
|
|
||||||
## Worker Preferences
|
## Worker Preferences
|
||||||
|
|||||||
16
Dockerfile
16
Dockerfile
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1.7
|
# syntax=docker/dockerfile:1.7
|
||||||
#
|
#
|
||||||
# mCables — single-stage build → distroless runtime image.
|
# CableGUI — single-stage build → distroless runtime image.
|
||||||
# go.mod requires go 1.25; modernc.org/sqlite is pure Go so CGO_ENABLED=0
|
# go.mod requires go 1.25; modernc.org/sqlite is pure Go so CGO_ENABLED=0
|
||||||
# and a distroless/static runtime is all we need.
|
# and a distroless/static runtime is all we need.
|
||||||
|
|
||||||
@@ -17,20 +17,20 @@ COPY . .
|
|||||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||||
-trimpath \
|
-trimpath \
|
||||||
-ldflags="-s -w" \
|
-ldflags="-s -w" \
|
||||||
-o /out/mcables \
|
-o /out/cablegui \
|
||||||
./cmd/mcables
|
./cmd/cablegui
|
||||||
|
|
||||||
FROM gcr.io/distroless/static-debian12:nonroot
|
FROM gcr.io/distroless/static-debian12:nonroot
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /out/mcables /app/mcables
|
COPY --from=build /out/cablegui /app/cablegui
|
||||||
|
|
||||||
ENV MCABLES_ADDR=0.0.0.0:7777 \
|
ENV CABLEGUI_ADDR=0.0.0.0:7777 \
|
||||||
MCABLES_DB=/app/data/mcables.db
|
CABLEGUI_DB=/app/data/cablegui.db
|
||||||
|
|
||||||
EXPOSE 7777
|
EXPOSE 7777
|
||||||
# Run as UID:GID 1000:1000 to match m on mDock — the bind-mounted
|
# 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
|
# /home/m/stacks/cablegui/data is owned by m:m, so the container can write
|
||||||
# to it without chowning the host dir. distroless/static-debian12 accepts
|
# to it without chowning the host dir. distroless/static-debian12 accepts
|
||||||
# arbitrary numeric UIDs; the Go binary doesn't need a /etc/passwd entry.
|
# arbitrary numeric UIDs; the Go binary doesn't need a /etc/passwd entry.
|
||||||
USER 1000:1000
|
USER 1000:1000
|
||||||
ENTRYPOINT ["/app/mcables"]
|
ENTRYPOINT ["/app/cablegui"]
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -1,14 +1,14 @@
|
|||||||
.PHONY: build run test typecheck fmt clean
|
.PHONY: build run test typecheck fmt clean
|
||||||
|
|
||||||
BIN := bin/mcables
|
BIN := bin/cablegui
|
||||||
PKG := ./...
|
PKG := ./...
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@mkdir -p bin
|
@mkdir -p bin
|
||||||
go build -trimpath -ldflags="-s -w" -o $(BIN) ./cmd/mcables
|
go build -trimpath -ldflags="-s -w" -o $(BIN) ./cmd/cablegui
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go run ./cmd/mcables
|
go run ./cmd/cablegui
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -race $(PKG)
|
go test -race $(PKG)
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -1,9 +1,9 @@
|
|||||||
# mCables
|
# CableGUI
|
||||||
|
|
||||||
Cable-management **framework** for m's setup — visual web editor backed by
|
Cable-management **framework** for m's setup — visual web editor backed by
|
||||||
a single Go binary + SQLite, generating Excalidraw drawings via mExDraw.
|
a single Go binary + SQLite, generating Excalidraw drawings via mExDraw.
|
||||||
|
|
||||||
Each cable-managed environment (LOFT, OFFICE, …) is a separate mCables
|
Each cable-managed environment (LOFT, OFFICE, …) is a separate CableGUI
|
||||||
*project*; each project is backed by exactly one `.excalidraw` drawing on
|
*project*; each project is backed by exactly one `.excalidraw` drawing on
|
||||||
mxdrw.msbls.de.
|
mxdrw.msbls.de.
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ end-to-end; the SVG canvas is intentionally empty until slice 2.
|
|||||||
## Run it
|
## Run it
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go run ./cmd/mcables
|
go run ./cmd/cablegui
|
||||||
# open http://localhost:7777
|
# open http://localhost:7777
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -31,18 +31,18 @@ Or built:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
make build
|
make build
|
||||||
./bin/mcables
|
./bin/cablegui
|
||||||
```
|
```
|
||||||
|
|
||||||
The binary serves the frontend from an embedded `web/static/` and the
|
The binary serves the frontend from an embedded `web/static/` and the
|
||||||
JSON API under `/api/`. SQLite lives at `./data/mcables.db` by default.
|
JSON API under `/api/`. SQLite lives at `./data/cablegui.db` by default.
|
||||||
|
|
||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
| Var | Default | Notes |
|
| Var | Default | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `MCABLES_ADDR` | `0.0.0.0:7777` | Listen address. |
|
| `CABLEGUI_ADDR` | `0.0.0.0:7777` | Listen address. |
|
||||||
| `MCABLES_DB` | `./data/mcables.db` | SQLite path. Parent dir is created on boot. |
|
| `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_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_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. |
|
| `MEXDRAW_PASS` | (unset) | Password for the mxdrw HTTP Basic Auth on export. Required. |
|
||||||
@@ -78,37 +78,35 @@ DELETE /api/cable-types/:id ← 409 in_use if any cable references
|
|||||||
|
|
||||||
## Deploy to mDock
|
## Deploy to mDock
|
||||||
|
|
||||||
mCables runs on **mDock** at `http://mdock:7777` as a docker-compose
|
CableGUI runs on **mDock** at `http://mdock:7777` as a docker-compose
|
||||||
service under `/home/m/stacks/mcables/`. Pattern matches the other
|
service under `/home/m/stacks/cablegui/`. Pattern matches the other
|
||||||
mDock services (mgreen-journal, mgeo, msports-garmin, …) — no Dokploy,
|
mDock services (mgreen-journal, mgeo, msports-garmin, …) — no Dokploy,
|
||||||
no reverse proxy, LAN-trusted.
|
no reverse proxy, LAN-trusted.
|
||||||
|
|
||||||
### Manual deploy (first roll)
|
### Manual deploy
|
||||||
|
|
||||||
1. **Build + push the image** (from any host with docker; today the
|
1. **Build + push the image** (image now lives under `m/` in Gitea):
|
||||||
image lives in mAi's Gitea namespace because mAi doesn't have write
|
|
||||||
access to `m/`):
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker build -t mgit.msbls.de/mai/mcables:latest .
|
docker build -t mgit.msbls.de/m/cablegui:latest .
|
||||||
awk '/machine mgit.msbls.de/{getline; getline; print $2}' ~/.netrc-mai \
|
awk '/machine mgit.msbls.de/{getline; getline; print $2}' ~/.netrc \
|
||||||
| docker login mgit.msbls.de -u mAi --password-stdin
|
| docker login mgit.msbls.de -u m --password-stdin
|
||||||
docker push mgit.msbls.de/mai/mcables:latest
|
docker push mgit.msbls.de/m/cablegui:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Prepare directories on mDock** (one-time):
|
2. **Prepare directories on mDock** (one-time):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ssh mdock 'mkdir -p /home/m/stacks/mcables/data /home/m/secrets/mcables \
|
ssh mdock 'mkdir -p /home/m/stacks/cablegui/data /home/m/secrets/cablegui \
|
||||||
&& touch /home/m/secrets/mcables/.env \
|
&& touch /home/m/secrets/cablegui/.env \
|
||||||
&& chmod 0600 /home/m/secrets/mcables/.env'
|
&& chmod 0600 /home/m/secrets/cablegui/.env'
|
||||||
scp docker-compose.yml mdock:/home/m/stacks/mcables/docker-compose.yml
|
scp docker-compose.yml mdock:/home/m/stacks/cablegui/docker-compose.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Pull + start**:
|
3. **Pull + start**:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ssh mdock 'cd /home/m/stacks/mcables && docker compose pull && docker compose up -d'
|
ssh mdock 'cd /home/m/stacks/cablegui && docker compose pull && docker compose up -d'
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Verify** from any LAN host:
|
4. **Verify** from any LAN host:
|
||||||
@@ -119,11 +117,11 @@ no reverse proxy, LAN-trusted.
|
|||||||
```
|
```
|
||||||
|
|
||||||
To **update** to a new build: rebuild + push the image, then
|
To **update** to a new build: rebuild + push the image, then
|
||||||
`ssh mdock 'cd /home/m/stacks/mcables && docker compose pull && docker compose up -d'`.
|
`ssh mdock 'cd /home/m/stacks/cablegui && docker compose pull && docker compose up -d'`.
|
||||||
|
|
||||||
### Persistence
|
### Persistence
|
||||||
|
|
||||||
SQLite lives at `/home/m/stacks/mcables/data/mcables.db` on the host
|
SQLite lives at `/home/m/stacks/cablegui/data/cablegui.db` on the host
|
||||||
(bind-mounted into the container at `/app/data`). Container runs as
|
(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
|
UID 1000:1000 to align with `m:m` ownership on mDock — DB files end
|
||||||
up owned by `m`, the host user.
|
up owned by `m`, the host user.
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
"mgit.msbls.de/m/mcables/internal/server"
|
"mgit.msbls.de/m/cablegui/internal/server"
|
||||||
"mgit.msbls.de/m/mcables/web"
|
"mgit.msbls.de/m/cablegui/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := envOr("MCABLES_ADDR", "0.0.0.0:7777")
|
addr := envOr("CABLEGUI_ADDR", "0.0.0.0:7777")
|
||||||
dbPath := envOr("MCABLES_DB", "./data/mcables.db")
|
dbPath := envOr("CABLEGUI_DB", "./data/cablegui.db")
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {
|
||||||
log.Fatalf("mkdir data dir: %v", err)
|
log.Fatalf("mkdir data dir: %v", err)
|
||||||
@@ -41,7 +41,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Printf("mcables listening on %s (db=%s)", addr, dbPath)
|
log.Printf("cablegui listening on %s (db=%s)", addr, dbPath)
|
||||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Fatalf("listen: %v", err)
|
log.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
# mCables — production compose for mDock.
|
# CableGUI — production compose for mDock.
|
||||||
# Lives at /home/m/stacks/mcables/docker-compose.yml on mDock.
|
# Lives at /home/m/stacks/cablegui/docker-compose.yml on mDock.
|
||||||
# Matches the existing mDock service patterns (mgreen, mgeo, …).
|
# Matches the existing mDock service patterns (mgreen, mgeo, …).
|
||||||
|
|
||||||
services:
|
services:
|
||||||
mcables:
|
cablegui:
|
||||||
image: mgit.msbls.de/m/mcables:latest
|
image: mgit.msbls.de/m/cablegui:latest
|
||||||
container_name: mcables
|
container_name: cablegui
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "7777:7777"
|
- "7777:7777"
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Berlin
|
- TZ=Europe/Berlin
|
||||||
- MCABLES_ADDR=0.0.0.0:7777
|
- CABLEGUI_ADDR=0.0.0.0:7777
|
||||||
- MCABLES_DB=/app/data/mcables.db
|
- CABLEGUI_DB=/app/data/cablegui.db
|
||||||
env_file:
|
env_file:
|
||||||
# MEXDRAW_USER + MEXDRAW_PASS for the mxdrw HTTP Basic Auth on export.
|
# MEXDRAW_USER + MEXDRAW_PASS for the mxdrw HTTP Basic Auth on export.
|
||||||
- /home/m/secrets/mcables/.env
|
- /home/m/secrets/cablegui/.env
|
||||||
volumes:
|
volumes:
|
||||||
- /home/m/stacks/mcables/data:/app/data
|
- /home/m/stacks/cablegui/data:/app/data
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# mCables — Design v4.1
|
# CableGUI — Design v4.1
|
||||||
|
|
||||||
Cable-management **framework + solver** for m's setup. Inventor shift 1
|
Cable-management **framework + solver** for m's setup. Inventor shift 1
|
||||||
design, revised through v2 (rescope to multi-project framework), v3
|
design, revised through v2 (rescope to multi-project framework), v3
|
||||||
@@ -6,7 +6,7 @@ design, revised through v2 (rescope to multi-project framework), v3
|
|||||||
**v4.1 — six locked answers from m's v4 review**.
|
**v4.1 — six locked answers from m's v4 review**.
|
||||||
|
|
||||||
> **What changed in v4.1** (tight pass on v4)
|
> **What changed in v4.1** (tight pass on v4)
|
||||||
> 1. **mCables is a schematic, not a physical-routing tool.** Cables are
|
> 1. **CableGUI is a schematic, not a physical-routing tool.** Cables are
|
||||||
> straight lines between endpoints; the solver and the renderer do not
|
> straight lines between endpoints; the solver and the renderer do not
|
||||||
> care about paths, trunks, frame edges, or cable-tray polylines.
|
> care about paths, trunks, frame edges, or cable-tray polylines.
|
||||||
> "Maximum bundling" reduces to the v3 rule: **≥2 cables between the
|
> "Maximum bundling" reduces to the v3 rule: **≥2 cables between the
|
||||||
@@ -33,13 +33,13 @@ design, revised through v2 (rescope to multi-project framework), v3
|
|||||||
|
|
||||||
Sources: the live `Cable-Management.excalidraw` on mxdrw.msbls.de (used as
|
Sources: the live `Cable-Management.excalidraw` on mxdrw.msbls.de (used as
|
||||||
the *visual-grammar reference*, not a bootstrap import target),
|
the *visual-grammar reference*, not a bootstrap import target),
|
||||||
`mai-memory` (`mcables`, `m`), and the live mDock services for deploy
|
`mai-memory` (`cablegui`, `m`), and the live mDock services for deploy
|
||||||
conventions (§10). v4 driven by m's product-vision clarification:
|
conventions (§10). v4 driven by m's product-vision clarification:
|
||||||
|
|
||||||
> "we provide a cable manager — I say what devices we have, the app tells
|
> "we provide a cable manager — I say what devices we have, the app tells
|
||||||
> me how to bundle cables and how the most efficient connection looks like"
|
> me how to bundle cables and how the most efficient connection looks like"
|
||||||
|
|
||||||
mCables shifts from a manual draw-and-click editor to a **solver** that
|
CableGUI shifts from a manual draw-and-click editor to a **solver** that
|
||||||
takes a list of devices + the connections m needs and emits the cable
|
takes a list of devices + the connections m needs and emits the cable
|
||||||
plan + bundle recommendations. The manual editor stays (it's the only way
|
plan + bundle recommendations. The manual editor stays (it's the only way
|
||||||
to inspect + tweak the plan) but is no longer the primary surface.
|
to inspect + tweak the plan) but is no longer the primary surface.
|
||||||
@@ -60,7 +60,7 @@ to inspect + tweak the plan) but is no longer the primary surface.
|
|||||||
> without applying; default applies.
|
> without applying; default applies.
|
||||||
> - **Solver objective: maximum bundling** (§5b.1). Schematic only: when
|
> - **Solver objective: maximum bundling** (§5b.1). Schematic only: when
|
||||||
> two or more cables share the same endpoint pair, group them into one
|
> two or more cables share the same endpoint pair, group them into one
|
||||||
> bundle. No path or trunk geometry — mCables is a wiring schematic,
|
> bundle. No path or trunk geometry — CableGUI is a wiring schematic,
|
||||||
> not a routing tool. v4.1 strips all path/trunk language from the v4
|
> not a routing tool. v4.1 strips all path/trunk language from the v4
|
||||||
> draft.
|
> draft.
|
||||||
> - **UI: device-type dropdown** on device-create, **Connection
|
> - **UI: device-type dropdown** on device-create, **Connection
|
||||||
@@ -72,7 +72,7 @@ to inspect + tweak the plan) but is no longer the primary surface.
|
|||||||
> bundle-rendering polish.
|
> bundle-rendering polish.
|
||||||
>
|
>
|
||||||
> **What carried over from v3 (unchanged in v4)**
|
> **What carried over from v3 (unchanged in v4)**
|
||||||
> - mCables is a framework: top-level `projects`, each backed by one
|
> - CableGUI is a framework: top-level `projects`, each backed by one
|
||||||
> `.excalidraw` drawing. `UNIQUE(projects.name)`.
|
> `.excalidraw` drawing. `UNIQUE(projects.name)`.
|
||||||
> - `cable_types` is global. Migration 001 seeds Power/USB/HDMI/DP/RJ45.
|
> - `cable_types` is global. Migration 001 seeds Power/USB/HDMI/DP/RJ45.
|
||||||
> - `devices` UNIQUE(project_id, name); `frame_id` nullable; FrameRef
|
> - `devices` UNIQUE(project_id, name); `frame_id` nullable; FrameRef
|
||||||
@@ -81,8 +81,8 @@ to inspect + tweak the plan) but is no longer the primary surface.
|
|||||||
> - `projects.drawing_name` auto-defaults to `<name>.excalidraw`.
|
> - `projects.drawing_name` auto-defaults to `<name>.excalidraw`.
|
||||||
> - `DELETE /api/projects/:pid?confirm=<name>` guardrail.
|
> - `DELETE /api/projects/:pid?confirm=<name>` guardrail.
|
||||||
> - No cable inventory metadata; visual + connectivity structure only.
|
> - No cable inventory metadata; visual + connectivity structure only.
|
||||||
> - DB at `./data/mcables.db` (gitignored). Bind `0.0.0.0:7777` LAN, no auth.
|
> - DB at `./data/cablegui.db` (gitignored). Bind `0.0.0.0:7777` LAN, no auth.
|
||||||
> - Deploy on mDock under `/home/m/stacks/mcables/`, raw docker-compose.
|
> - Deploy on mDock under `/home/m/stacks/cablegui/`, raw docker-compose.
|
||||||
>
|
>
|
||||||
> **What's superseded in v4**
|
> **What's superseded in v4**
|
||||||
> - The "manual draw-a-cable port-to-port" flow from v3 §7 is *kept* as a
|
> - The "manual draw-a-cable port-to-port" flow from v3 §7 is *kept* as a
|
||||||
@@ -97,7 +97,7 @@ to inspect + tweak the plan) but is no longer the primary surface.
|
|||||||
|
|
||||||
`Cable-Management.excalidraw` on mxdrw.msbls.de is **not** ingested at
|
`Cable-Management.excalidraw` on mxdrw.msbls.de is **not** ingested at
|
||||||
runtime. It is the visual-grammar reference we lock the export onto so that
|
runtime. It is the visual-grammar reference we lock the export onto so that
|
||||||
when m rebuilds LOFT and OFFICE inside mCables, the exported `.excalidraw`
|
when m rebuilds LOFT and OFFICE inside CableGUI, the exported `.excalidraw`
|
||||||
looks like the seed.
|
looks like the seed.
|
||||||
|
|
||||||
Concrete numbers from the live file (180 elements):
|
Concrete numbers from the live file (180 elements):
|
||||||
@@ -128,7 +128,7 @@ Three observations about the seed's visual grammar — these constrain the
|
|||||||
1. **Ports sit on a device edge as small ellipses (~12×9)**, coloured by
|
1. **Ports sit on a device edge as small ellipses (~12×9)**, coloured by
|
||||||
cable type. They are not children of the device in the Excalidraw sense
|
cable type. They are not children of the device in the Excalidraw sense
|
||||||
(no `containerId`/`boundElements` link) — purely positional. When we
|
(no `containerId`/`boundElements` link) — purely positional. When we
|
||||||
export from mCables we mimic that: port ellipse at `(device.x +
|
export from CableGUI we mimic that: port ellipse at `(device.x +
|
||||||
port.x_offset, device.y + port.y_offset)`, stroke colour = type colour.
|
port.x_offset, device.y + port.y_offset)`, stroke colour = type colour.
|
||||||
2. **Cable arrows bind to elements**. In the seed: 44 endpoints to ellipses
|
2. **Cable arrows bind to elements**. In the seed: 44 endpoints to ellipses
|
||||||
(ports), 12 to whole rectangles (device-level, no specific port), 3 to
|
(ports), 12 to whole rectangles (device-level, no specific port), 3 to
|
||||||
@@ -161,7 +161,7 @@ painful: switch to Preact-via-CDN-ESM (still no build step). Not v0.
|
|||||||
|
|
||||||
## 2. SQLite schema
|
## 2. SQLite schema
|
||||||
|
|
||||||
`./data/mcables.db` (project-local, gitignored). WAL mode, FKs on.
|
`./data/cablegui.db` (project-local, gitignored). WAL mode, FKs on.
|
||||||
Driver: **`modernc.org/sqlite`** (cgo-free — clean cross-compile, simple
|
Driver: **`modernc.org/sqlite`** (cgo-free — clean cross-compile, simple
|
||||||
Dockerfile).
|
Dockerfile).
|
||||||
|
|
||||||
@@ -609,8 +609,8 @@ cascade does **not** touch `cable_types` (no FK to projects).
|
|||||||
|
|
||||||
## 3. Go HTTP API
|
## 3. Go HTTP API
|
||||||
|
|
||||||
Single binary `cmd/mcables`, `net/http`, no router framework. Listens on
|
Single binary `cmd/cablegui`, `net/http`, no router framework. Listens on
|
||||||
`0.0.0.0:7777` by default (overridable via `MCABLES_ADDR`). Static frontend
|
`0.0.0.0:7777` by default (overridable via `CABLEGUI_ADDR`). Static frontend
|
||||||
from `embed.FS` at `/`, JSON API under `/api/`.
|
from `embed.FS` at `/`, JSON API under `/api/`.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -780,7 +780,7 @@ generated scene JSON.
|
|||||||
|
|
||||||
## 4. Export — DB → Excalidraw (visual-grammar conformance)
|
## 4. Export — DB → Excalidraw (visual-grammar conformance)
|
||||||
|
|
||||||
mCables generates a `.excalidraw` scene from a project's rows. The seed
|
CableGUI generates a `.excalidraw` scene from a project's rows. The seed
|
||||||
drawing's grammar is the contract.
|
drawing's grammar is the contract.
|
||||||
|
|
||||||
### 4.1 Element mapping
|
### 4.1 Element mapping
|
||||||
@@ -798,7 +798,7 @@ drawing's grammar is the contract.
|
|||||||
|
|
||||||
### 4.2 Element IDs are stable across exports
|
### 4.2 Element IDs are stable across exports
|
||||||
|
|
||||||
Every mCables row carries `excalidraw_id` (TEXT, generated on first export
|
Every CableGUI row carries `excalidraw_id` (TEXT, generated on first export
|
||||||
via `crypto/rand` → 21-char Excalidraw-style ID). On re-export the same row
|
via `crypto/rand` → 21-char Excalidraw-style ID). On re-export the same row
|
||||||
reuses the same ID. This means:
|
reuses the same ID. This means:
|
||||||
|
|
||||||
@@ -866,7 +866,7 @@ left strictly alone — the solver only adds and removes its own.
|
|||||||
|
|
||||||
### 5b.1 Objective: maximum bundling — schematic only
|
### 5b.1 Objective: maximum bundling — schematic only
|
||||||
|
|
||||||
mCables is a **schematic**, not a physical-routing tool. Cables are
|
CableGUI is a **schematic**, not a physical-routing tool. Cables are
|
||||||
straight lines between endpoints; the solver has no model of walls,
|
straight lines between endpoints; the solver has no model of walls,
|
||||||
floors, cable trays, or path geometry. "Maximum bundling" therefore
|
floors, cable trays, or path geometry. "Maximum bundling" therefore
|
||||||
reduces to a single rule on the schematic:
|
reduces to a single rule on the schematic:
|
||||||
@@ -988,7 +988,7 @@ triggers a debounced re-solve) is parked at slice 9+ as an opt-in.
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────┐
|
┌─────────────────────┐
|
||||||
│ mCables DB (truth) │
|
│ CableGUI DB (truth) │
|
||||||
└──────────┬──────────┘
|
└──────────┬──────────┘
|
||||||
│
|
│
|
||||||
export ▼
|
export ▼
|
||||||
@@ -998,11 +998,11 @@ triggers a debounced re-solve) is parked at slice 9+ as an opt-in.
|
|||||||
└────────────────────────┘
|
└────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
- mCables UI → DB: synchronous (every drag/add/remove persists immediately).
|
- CableGUI UI → DB: synchronous (every drag/add/remove persists immediately).
|
||||||
- DB → Excalidraw: **manual** button "Export to Excalidraw" in the header,
|
- DB → Excalidraw: **manual** button "Export to Excalidraw" in the header,
|
||||||
per project. Calls `POST /api/projects/:pid/sync/export`.
|
per project. Calls `POST /api/projects/:pid/sync/export`.
|
||||||
- Excalidraw → DB: **not implemented** in v0. Anything m draws in
|
- Excalidraw → DB: **not implemented** in v0. Anything m draws in
|
||||||
Excalidraw stays in Excalidraw until he redraws it in mCables.
|
Excalidraw stays in Excalidraw until he redraws it in CableGUI.
|
||||||
|
|
||||||
This keeps the v0 scope tight: no conflict resolution, no element-diff
|
This keeps the v0 scope tight: no conflict resolution, no element-diff
|
||||||
import, no auto-debounce. mExDraw keeps its own version history (git
|
import, no auto-debounce. mExDraw keeps its own version history (git
|
||||||
@@ -1012,7 +1012,7 @@ When mxdrw is unreachable: the export button shows a tooltip and disables;
|
|||||||
the editor keeps working against the local DB.
|
the editor keeps working against the local DB.
|
||||||
|
|
||||||
Post-MVP, import returns as a one-shot migration tool (separate
|
Post-MVP, import returns as a one-shot migration tool (separate
|
||||||
`mcables-migrate` CLI tool, not part of the running server) for seeding
|
`cablegui-migrate` CLI tool, not part of the running server) for seeding
|
||||||
new projects from existing `.excalidraw` files.
|
new projects from existing `.excalidraw` files.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -1023,7 +1023,7 @@ The editor lives at `/`. Layout:
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌────────────────────────────────────────────────────────────────────┐
|
┌────────────────────────────────────────────────────────────────────┐
|
||||||
│ mCables [LOFT ▾ projects-picker] [Export] [+ Project] │ ← header
|
│ CableGUI [LOFT ▾ projects-picker] [Export] [+ Project] │ ← header
|
||||||
├────────┬───────────────────────────────────────────────────────────┤
|
├────────┬───────────────────────────────────────────────────────────┤
|
||||||
│ │ │
|
│ │ │
|
||||||
│ Legend │ │
|
│ Legend │ │
|
||||||
@@ -1254,7 +1254,7 @@ Slices 9+ (not promised for the first coder shift):
|
|||||||
- Cable inventory metadata (length/SKU) if m later wants it.
|
- Cable inventory metadata (length/SKU) if m later wants it.
|
||||||
- Dark mode.
|
- Dark mode.
|
||||||
|
|
||||||
Out of scope, period (would change mCables's mental model): path
|
Out of scope, period (would change CableGUI's mental model): path
|
||||||
routing, cable-tray polylines, frame-edge corridors, wall-axis bundling,
|
routing, cable-tray polylines, frame-edge corridors, wall-axis bundling,
|
||||||
3D, anything that treats a cable as more than a labelled endpoint pair.
|
3D, anything that treats a cable as more than a labelled endpoint pair.
|
||||||
|
|
||||||
@@ -1264,7 +1264,7 @@ routing, cable-tray polylines, frame-edge corridors, wall-axis bundling,
|
|||||||
|
|
||||||
The six v4 questions are now answered. Locked answers:
|
The six v4 questions are now answered. Locked answers:
|
||||||
|
|
||||||
1. **Where do paths come from?** → **Nowhere — mCables is a schematic.**
|
1. **Where do paths come from?** → **Nowhere — CableGUI is a schematic.**
|
||||||
Cables are straight lines between endpoints. The solver does not
|
Cables are straight lines between endpoints. The solver does not
|
||||||
route, the renderer does not route, and "maximum bundling" reduces to
|
route, the renderer does not route, and "maximum bundling" reduces to
|
||||||
the endpoint-pair rule (§5b.1). Anything resembling a path, trunk,
|
the endpoint-pair rule (§5b.1). Anything resembling a path, trunk,
|
||||||
@@ -1307,25 +1307,25 @@ before writing this:
|
|||||||
- Host port mappings: deliberately collision-free across the host. Existing
|
- Host port mappings: deliberately collision-free across the host. Existing
|
||||||
high ports in use include 3300 (mgreen), 3077 (paperless-ai), 7878
|
high ports in use include 3300 (mgreen), 3077 (paperless-ai), 7878
|
||||||
(radarr), 8082 (mgeo-tileserver), 8989 (sonarr), 9696 (prowlarr).
|
(radarr), 8082 (mgeo-tileserver), 8989 (sonarr), 9696 (prowlarr).
|
||||||
**Port 7777 is free** — taking it for mCables.
|
**Port 7777 is free** — taking it for CableGUI.
|
||||||
- Bind-mount volumes: `/home/m/<project>-data:/app/data` is the canonical
|
- Bind-mount volumes: `/home/m/<project>-data:/app/data` is the canonical
|
||||||
pattern (mgreen). For project-local data we put `data/` *next to* the
|
pattern (mgreen). For project-local data we put `data/` *next to* the
|
||||||
compose file so a `git pull && docker compose up -d` is the whole deploy:
|
compose file so a `git pull && docker compose up -d` is the whole deploy:
|
||||||
`/home/m/stacks/mcables/data:/app/data`.
|
`/home/m/stacks/cablegui/data:/app/data`.
|
||||||
- Secrets via `env_file: /home/m/secrets/<project>/.env` (msports-garmin
|
- Secrets via `env_file: /home/m/secrets/<project>/.env` (msports-garmin
|
||||||
pattern). mCables only needs `MEXDRAW_TOKEN` for export.
|
pattern). CableGUI only needs `MEXDRAW_TOKEN` for export.
|
||||||
- No reverse proxy on mDock. Services expose ports directly on the LAN
|
- No reverse proxy on mDock. Services expose ports directly on the LAN
|
||||||
(mDock = `192.168.178.131` / Tailscale `mdock`). Public exposure goes via
|
(mDock = `192.168.178.131` / Tailscale `mdock`). Public exposure goes via
|
||||||
mlake/Dokploy + Caddy when needed — out of scope for mCables (LAN-only).
|
mlake/Dokploy + Caddy when needed — out of scope for CableGUI (LAN-only).
|
||||||
- Auto-deploy via the Gitea Actions self-hosted runner already installed
|
- Auto-deploy via the Gitea Actions self-hosted runner already installed
|
||||||
on mDock (`/home/m/act-runner/`, label `self-hosted:host`). Push to
|
on mDock (`/home/m/act-runner/`, label `self-hosted:host`). Push to
|
||||||
`main` → workflow on mDock → `docker compose up --build -d`.
|
`main` → workflow on mDock → `docker compose up --build -d`.
|
||||||
|
|
||||||
### Repo layout for mCables
|
### Repo layout for CableGUI
|
||||||
|
|
||||||
```
|
```
|
||||||
mCables/
|
CableGUI/
|
||||||
├── cmd/mcables/main.go # Go binary
|
├── cmd/cablegui/main.go # Go binary
|
||||||
├── internal/
|
├── internal/
|
||||||
│ ├── db/ # migrations + store
|
│ ├── db/ # migrations + store
|
||||||
│ ├── importer/ # post-MVP only (not in MVP)
|
│ ├── importer/ # post-MVP only (not in MVP)
|
||||||
@@ -1336,7 +1336,7 @@ mCables/
|
|||||||
│ ├── main.js # ES module entry
|
│ ├── main.js # ES module entry
|
||||||
│ ├── style.css
|
│ ├── style.css
|
||||||
│ └── lib/... # SVG helpers, store, components
|
│ └── lib/... # SVG helpers, store, components
|
||||||
├── data/ # mCables runtime DB lives here (gitignored)
|
├── data/ # CableGUI runtime DB lives here (gitignored)
|
||||||
│ └── .gitkeep
|
│ └── .gitkeep
|
||||||
├── docs/design.md # this file
|
├── docs/design.md # this file
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
@@ -1361,37 +1361,37 @@ COPY go.mod go.sum ./
|
|||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" \
|
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" \
|
||||||
-o /out/mcables ./cmd/mcables
|
-o /out/cablegui ./cmd/cablegui
|
||||||
|
|
||||||
FROM gcr.io/distroless/static-debian12:nonroot
|
FROM gcr.io/distroless/static-debian12:nonroot
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /out/mcables /app/mcables
|
COPY --from=build /out/cablegui /app/cablegui
|
||||||
ENV MCABLES_ADDR=0.0.0.0:7777
|
ENV CABLEGUI_ADDR=0.0.0.0:7777
|
||||||
ENV MCABLES_DB=/app/data/mcables.db
|
ENV CABLEGUI_DB=/app/data/cablegui.db
|
||||||
USER nonroot:nonroot
|
USER nonroot:nonroot
|
||||||
EXPOSE 7777
|
EXPOSE 7777
|
||||||
ENTRYPOINT ["/app/mcables"]
|
ENTRYPOINT ["/app/cablegui"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### docker-compose.yml (on mDock at `/home/m/stacks/mcables/`)
|
### docker-compose.yml (on mDock at `/home/m/stacks/cablegui/`)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
mcables:
|
cablegui:
|
||||||
image: mgit.msbls.de/m/mcables:latest
|
image: mgit.msbls.de/m/cablegui:latest
|
||||||
container_name: mcables
|
container_name: cablegui
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "7777:7777"
|
- "7777:7777"
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Berlin
|
- TZ=Europe/Berlin
|
||||||
- MCABLES_ADDR=0.0.0.0:7777
|
- CABLEGUI_ADDR=0.0.0.0:7777
|
||||||
- MCABLES_DB=/app/data/mcables.db
|
- CABLEGUI_DB=/app/data/cablegui.db
|
||||||
- MEXDRAW_BASE_URL=https://mxdrw.msbls.de
|
- MEXDRAW_BASE_URL=https://mxdrw.msbls.de
|
||||||
env_file:
|
env_file:
|
||||||
- /home/m/secrets/mcables/.env # contains MEXDRAW_TOKEN
|
- /home/m/secrets/cablegui/.env # contains MEXDRAW_TOKEN
|
||||||
volumes:
|
volumes:
|
||||||
- /home/m/stacks/mcables/data:/app/data
|
- /home/m/stacks/cablegui/data:/app/data
|
||||||
```
|
```
|
||||||
|
|
||||||
LAN URL: `http://mdock:7777` (or `http://192.168.178.131:7777`).
|
LAN URL: `http://mdock:7777` (or `http://192.168.178.131:7777`).
|
||||||
@@ -1412,15 +1412,15 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: docker build -t mgit.msbls.de/m/mcables:latest .
|
run: docker build -t mgit.msbls.de/m/cablegui:latest .
|
||||||
- name: Push image
|
- name: Push image
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.GITEA_TOKEN }}" | \
|
echo "${{ secrets.GITEA_TOKEN }}" | \
|
||||||
docker login mgit.msbls.de -u mAi --password-stdin
|
docker login mgit.msbls.de -u mAi --password-stdin
|
||||||
docker push mgit.msbls.de/m/mcables:latest
|
docker push mgit.msbls.de/m/cablegui:latest
|
||||||
- name: Up
|
- name: Up
|
||||||
run: |
|
run: |
|
||||||
cd /home/m/stacks/mcables
|
cd /home/m/stacks/cablegui
|
||||||
docker compose pull
|
docker compose pull
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
@@ -1428,7 +1428,7 @@ jobs:
|
|||||||
### Local-development run (no Docker)
|
### Local-development run (no Docker)
|
||||||
|
|
||||||
```
|
```
|
||||||
make run # go run ./cmd/mcables → :7777 against ./data/mcables.db
|
make run # go run ./cmd/cablegui → :7777 against ./data/cablegui.db
|
||||||
make typecheck # tsc --noEmit on web/
|
make typecheck # tsc --noEmit on web/
|
||||||
make test # go test ./...
|
make test # go test ./...
|
||||||
```
|
```
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module mgit.msbls.de/m/mcables
|
module mgit.msbls.de/m/cablegui
|
||||||
|
|
||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package db owns SQLite access for mCables: migrations runner + the
|
// Package db owns SQLite access for CableGUI: migrations runner + the
|
||||||
// query layer (store.go). The Store wraps a *sql.DB with helpers; tests
|
// query layer (store.go). The Store wraps a *sql.DB with helpers; tests
|
||||||
// and the HTTP layer take a *Store, never a raw *sql.DB.
|
// and the HTTP layer take a *Store, never a raw *sql.DB.
|
||||||
package db
|
package db
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- mCables v3 initial schema. See docs/design.md §2.
|
-- CableGUI v3 initial schema. See docs/design.md §2.
|
||||||
|
|
||||||
-- A project IS a drawing. LOFT and OFFICE are separate projects.
|
-- A project IS a drawing. LOFT and OFFICE are separate projects.
|
||||||
-- One project ↔ one .excalidraw file in mExDraw.
|
-- One project ↔ one .excalidraw file in mExDraw.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- mCables v4 device-type catalog. See docs/design.md §2.1 + §2.2.
|
-- CableGUI v4 device-type catalog. See docs/design.md §2.1 + §2.2.
|
||||||
|
|
||||||
-- v4 — device-type catalog. Built-in types live globally (project_id NULL).
|
-- v4 — device-type catalog. Built-in types live globally (project_id NULL).
|
||||||
-- Per-project custom types use project_id = X.
|
-- Per-project custom types use project_id = X.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- mCables v4.1 connection requirements + solver-owned cable flag.
|
-- CableGUI v4.1 connection requirements + solver-owned cable flag.
|
||||||
-- See docs/design.md §2.1 + §2 connection_requirements + §5b.3.
|
-- See docs/design.md §2.1 + §2 connection_requirements + §5b.3.
|
||||||
|
|
||||||
-- The solver's input: "device A must connect to device B via cable type T".
|
-- The solver's input: "device A must connect to device B via cable type T".
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- mCables v4.1 setup templates. See docs/design.md §2.4.
|
-- CableGUI v4.1 setup templates. See docs/design.md §2.4.
|
||||||
--
|
--
|
||||||
-- A template is a named recipe of (device_types + requirements) that
|
-- A template is a named recipe of (device_types + requirements) that
|
||||||
-- bootstraps a project from blank to solver-ready in one apply call.
|
-- bootstraps a project from blank to solver-ready in one apply call.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- mCables v5 — catalog: power-distribution devices.
|
-- CableGUI v5 — catalog: power-distribution devices.
|
||||||
-- Adds 5 built-in device_types (project_id NULL, built_in=1).
|
-- Adds 5 built-in device_types (project_id NULL, built_in=1).
|
||||||
--
|
--
|
||||||
-- Multi-plug N exposes Power × (N+1) ports — one input + N outputs. The
|
-- Multi-plug N exposes Power × (N+1) ports — one input + N outputs. The
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- mCables v6 — fix IOx-* and Multi-plug-* + Wifi-plug port profiles.
|
-- CableGUI v6 — fix IOx-* and Multi-plug-* + Wifi-plug port profiles.
|
||||||
--
|
--
|
||||||
-- v4 seeded the IOx-3 / IOx-6 / IOx-8 as USB hubs (Power × 1 + USB × N),
|
-- v4 seeded the IOx-3 / IOx-6 / IOx-8 as USB hubs (Power × 1 + USB × N),
|
||||||
-- but m's physical IOx-* devices are power strips (1 power input on
|
-- but m's physical IOx-* devices are power strips (1 power input on
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-- mCables v5 — cable routing via clamps. See docs/design.md §11.
|
-- CableGUI v5 — cable routing via clamps. See docs/design.md §11.
|
||||||
--
|
--
|
||||||
-- A clamp is a physical anchor placed on the canvas. A cable's polyline
|
-- A clamp is a physical anchor placed on the canvas. A cable's polyline
|
||||||
-- runs from its `from` endpoint → its clamps in `ord` sequence → its
|
-- runs from its `from` endpoint → its clamps in `ord` sequence → its
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scene is the top-level Excalidraw file format. Keys mirror what the
|
// Scene is the top-level Excalidraw file format. Keys mirror what the
|
||||||
@@ -537,7 +537,7 @@ func BuildScene(snap *db.Snapshot, nowMilli int64, genID func() string) (*Scene,
|
|||||||
scene := &Scene{
|
scene := &Scene{
|
||||||
Type: "excalidraw",
|
Type: "excalidraw",
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Source: "mcables",
|
Source: "cablegui",
|
||||||
Elements: els,
|
Elements: els,
|
||||||
AppState: AppState{
|
AppState: AppState{
|
||||||
GridSize: nil,
|
GridSize: nil,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// deterministic id generator for tests
|
// deterministic id generator for tests
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cableEndpointBody struct {
|
type cableEndpointBody struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type clampCreate struct {
|
type clampCreate struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type connReqCreate struct {
|
type connReqCreate struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type deviceTypePortBody struct {
|
type deviceTypePortBody struct {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
"mgit.msbls.de/m/mcables/internal/exporter"
|
"mgit.msbls.de/m/cablegui/internal/exporter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// syncExport runs the project's snapshot through the exporter, persists
|
// syncExport runs the project's snapshot through the exporter, persists
|
||||||
@@ -34,7 +34,7 @@ func (h *handlers) syncExport(w http.ResponseWriter, r *http.Request) {
|
|||||||
if user == "" || pass == "" {
|
if user == "" || pass == "" {
|
||||||
writeJSON(w, http.StatusBadRequest, errorBody{
|
writeJSON(w, http.StatusBadRequest, errorBody{
|
||||||
Error: "MEXDRAW_USER / MEXDRAW_PASS not set",
|
Error: "MEXDRAW_USER / MEXDRAW_PASS not set",
|
||||||
Details: "Add MEXDRAW_USER and MEXDRAW_PASS to /home/m/secrets/mcables/.env on mDock and restart the container — mxdrw expects HTTP Basic Auth",
|
Details: "Add MEXDRAW_USER and MEXDRAW_PASS to /home/m/secrets/cablegui/.env on mDock and restart the container — mxdrw expects HTTP Basic Auth",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------- frames
|
// ---------------------------------------------------------------- frames
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type handlers struct {
|
type handlers struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ioMarkerCreate struct {
|
type ioMarkerCreate struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type portCreate struct {
|
type portCreate struct {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New returns an http.Handler serving the mCables API at /api/ and the
|
// New returns an http.Handler serving the CableGUI API at /api/ and the
|
||||||
// embedded frontend at /. The frontend FS should be rooted such that
|
// embedded frontend at /. The frontend FS should be rooted such that
|
||||||
// "index.html" is at its root.
|
// "index.html" is at its root.
|
||||||
func New(store *db.Store, frontend fs.FS) http.Handler {
|
func New(store *db.Store, frontend fs.FS) http.Handler {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mgit.msbls.de/m/mcables/internal/db"
|
"mgit.msbls.de/m/cablegui/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *handlers) solve(w http.ResponseWriter, r *http.Request) {
|
func (h *handlers) solve(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>mCables</title>
|
<title>CableGUI</title>
|
||||||
<link rel="stylesheet" href="/style.css" />
|
<link rel="stylesheet" href="/style.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<span class="brand">mCables</span>
|
<span class="brand">CableGUI</span>
|
||||||
<div class="project-picker">
|
<div class="project-picker">
|
||||||
<label for="project-select" class="sr-only">Project</label>
|
<label for="project-select" class="sr-only">Project</label>
|
||||||
<select id="project-select" aria-label="Active project">
|
<select id="project-select" aria-label="Active project">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// mCables frontend entry — vanilla ES module, no build step.
|
// CableGUI frontend entry — vanilla ES module, no build step.
|
||||||
//
|
//
|
||||||
// Slice 2 adds: frame + device rendering, +Frm/+Dev tools, drag-to-position,
|
// Slice 2 adds: frame + device rendering, +Frm/+Dev tools, drag-to-position,
|
||||||
// inline naming, inspector for selection. State stays minimal: one
|
// inline naming, inspector for selection. State stays minimal: one
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package web bundles the frontend (HTML/JS/CSS) into the Go binary
|
// Package web bundles the frontend (HTML/JS/CSS) into the Go binary
|
||||||
// via embed.FS so deploying mCables means shipping one file.
|
// via embed.FS so deploying CableGUI means shipping one file.
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
Reference in New Issue
Block a user