Commit graph

132 commits

Author SHA1 Message Date
dlawler489
ac88a247ae Add ESLint, and run it with the tests in CI
A variable declared inside a try block but read after it shipped and broke
collection checks for a day; tests can't reach that path and node --check
only parses. ESLint flags it as no-undef. Wired into npm run lint / check
and into CI before the image build, so a failure stops the build.

Three findings fixed on the way: an unread argument, and two values
assigned then immediately replaced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 11:40:43 +10:00
dlawler489
517574720e Merge watch check fix
All checks were successful
build-and-push / docker (push) Successful in 15s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 10:48:45 +10:00
dlawler489
ca0d0d52f5 Fix a watch check stopping after one model
withFiles was declared inside the try block but read by the wait after it,
so every check threw ReferenceError once it had imported a single model.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 10:48:45 +10:00
dlawler489
4956eabbf4 Merge data path fix
All checks were successful
build-and-push / docker (push) Successful in 23s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 17:00:29 +10:00
dlawler489
9e92be8407 Pin the data paths in compose, and find the files via the container
A stack manager that deploys from its own directory resolved ./data to an
empty folder, so Postgres initialised a blank database and the install
looked wiped. The compose defaults are absolute now, and survive a sync
that deletes an untracked .env.

The backup script asks the running container where /app/uploads comes from
rather than trusting a setting, refuses to mirror an empty folder, and
won't write a dump of an empty library over backups that have one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:59:13 +10:00
dlawler489
0844c9f86a Merge backup settings fix
All checks were successful
build-and-push / docker (push) Successful in 16s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:49:33 +10:00
dlawler489
217671a5ef Read .env settings tolerantly, and check the files folder first
The backup script missed DATA_DIR when the line carried an export prefix,
quotes, padding or a CR, and silently fell back to ./data — dumping first
and only then discovering there was nothing to mirror. It now strips all
that, prints the paths it's using, and stops before writing anything if
the files folder isn't there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:49:33 +10:00
dlawler489
22645f139a Merge nightly backup script
All checks were successful
build-and-push / docker (push) Successful in 15s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:44:43 +10:00
dlawler489
bc71c19a40 Keep backups out of git
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:19:26 +10:00
dlawler489
a5bb4dc33d Add a nightly backup script
A dated pg_dump plus an rsync mirror of the model files, with the stack
running. The dump is read back with pg_restore -l inside the container
before it replaces the day's file, so a bad run leaves older backups
alone. Older dumps are pruned after KEEP_DAYS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:19:21 +10:00
dlawler489
01a6688900 Merge host folders for data
All checks were successful
build-and-push / docker (push) Successful in 17s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:10:34 +10:00
dlawler489
6c10cf6b37 Let the data folders be set per install, database separately
DATA_DIR takes the files and PGDATA_DIR the database, so the database can
stay on a filesystem that supports ownership when the files live on a disk
that doesn't. The example .env points both at /Volumes/Data/printhive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 16:01:43 +10:00
dlawler489
985b81d1de Keep data in host folders instead of Docker named volumes
The database and files now live under ./data (DATA_DIR to move them), so
they can be seen, backed up and restored without going through Docker.
tools/volumes-to-folders.sh copies an existing install's named volumes
across; it only copies, leaving the volumes in place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:58:28 +10:00
dlawler489
d5fa67d9e7 Merge MakerWorld anti-robot handling
All checks were successful
build-and-push / docker (push) Successful in 55s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:48:36 +10:00
dlawler489
08d9bdabb1 Note the anti-robot check in the README
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:45:09 +10:00
dlawler489
ef2093b73f Say when MakerWorld is waiting for you to prove you're not a robot
The challenge was detected but treated as an ordinary download failure, so
the message didn't say that only you can clear it. Now it stops the run,
is remembered centrally, and watches skip downloads while it stands —
metadata still imports, and models stay partial for "Get files". The
Watched page shows a banner with a link to MakerWorld and an "I've solved
it" button; a successful download clears it too. Detection also covers a
challenge page returned where JSON was expected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:44:54 +10:00
dlawler489
bbbaf8fa7d Merge chamber camera view
All checks were successful
build-and-push / docker (push) Successful in 16s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 15:08:01 +10:00
dlawler489
a91c7b4bf7 Show the chamber camera on the printer card
A Camera button on each card opens the printer's chamber view, served
through Printhive so the access code stays on the server. One connection
per printer however many people are watching, dropped as soon as the last
viewer closes it; the latest frame goes to a new viewer straight away.
Nothing is recorded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 15:07:22 +10:00
dlawler489
a8fd112257 Merge chamber camera connection and test
All checks were successful
build-and-push / docker (push) Successful in 17s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 15:04:32 +10:00
dlawler489
a442d31dc7 Add a chamber camera connection and a test for it
P1 and A1 series printers serve JPEG frames on port 6000 over TLS, behind
the same access code. This adds the connection, a frame reader, and
POST /api/printers/:id/camera/test, which waits for one picture and says
what arrived — enough to find out whether the camera answers before any
viewer is built.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 11:47:09 +10:00
dlawler489
f7f7b21d36 Merge active filament display
All checks were successful
build-and-push / docker (push) Successful in 14s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 00:31:53 +10:00
dlawler489
1aca9a6c01 Show which filament is feeding each printer, with a tube from spool to toolhead
The live connection reads tray_now (unit × 4 + slot, 254 external, 255
none) and works out the unit by id or, failing that, by position — so the
A2L's AMS lite numbered 16 is handled. Cards say 'Using' (or 'Loaded' when
not printing) with the filament's name and slot, mark the slot in the list,
and draw a tube in its colour from that spool to the toolhead, with a
moving flow while printing. The external spool is drawn when it's in use.
Debug reports include tray_now and tray_tar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 00:27:57 +10:00
dlawler489
859554e345 Merge page and model addresses
All checks were successful
build-and-push / docker (push) Successful in 15s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 00:21:26 +10:00
dlawler489
f3e0a645a2 Keep the open model in the address too
Opening a model adds /model/<id> to the address (#collection/12/model/34),
so a refresh or bookmark reopens it and Back closes it. Moving from one
model to the next doesn't add Back steps, and closing takes back the step
opening added. A model that's gone is dropped from the address.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 00:21:03 +10:00
dlawler489
2aff27cbdd Keep the page you're on in the address
Each page has an address (#filament, #printers, #collection/12), so a refresh
or bookmark returns to it and Back/Forward move between pages. The search
and filter bar is hidden on the Filament page, where it did nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 00:19:13 +10:00
dlawler489
ff2b0bc11f Merge live status fixes and untagged spool naming
All checks were successful
build-and-push / docker (push) Successful in 14s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:55:16 +10:00
dlawler489
73176cc41d Call untagged spools by their material and reported colour, not a Bambu colour name
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:55:16 +10:00
dlawler489
88988237ae Don't let a busy printer's reports hold back spool sync; hide placeholder chamber temps
A printer mid-print reports every second, and each report restarted the
five-second wait, so a printing printer's spools never synced. Only a change
in what's loaded restarts it now. Printers without a chamber sensor report
5 °C, which is no longer shown.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:50:54 +10:00
dlawler489
4daf15d390 Merge live printer status
All checks were successful
build-and-push / docker (push) Successful in 16s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:46:50 +10:00
dlawler489
13b5e550a3 Show live printer status over a standing read-only connection
Each printer now has one MQTT connection that stays open, merging the
partial reports printers send while printing. Cards poll every 3 seconds
(paused while the tab is hidden) and show state, file, progress with time
left and finish time, layer, nozzle/bed/chamber temperatures, speed
setting, and print error and HMS codes. A spool change syncs the Filament
page and the card's drawing within seconds; a full report is requested
every 10 minutes as a backstop, replacing the 10-minute probe loop.

"Test & sync" uses the open connection rather than making a second one
(?debug=1 still makes a short separate one). Connections are reconciled
when printers are added, edited or removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:30:13 +10:00
dlawler489
c7d4194243 Merge loaded filament on printer cards
All checks were successful
build-and-push / docker (push) Successful in 14s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:23:22 +10:00
dlawler489
c2bae07e54 Show each printer's loaded filament on its card
Each sync keeps what the printer reported, slot by slot (material, colour,
empty, tagged — nothing that identifies a spool). The card's drawing colours
in the first AMS unit's spools, dashes empty slots, and lists every slot and
the external holder underneath with its colour name. "Test & sync" redraws
the card with what it just read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:21:03 +10:00
dlawler489
99e0f3d245 Merge AMS unit labels
All checks were successful
build-and-push / docker (push) Successful in 16s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:18:56 +10:00
dlawler489
fdb50d45e5 Label AMS units by their range, so the A2L's AMS lite isn't 'AMS 17'
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:14:03 +10:00
dlawler489
697ac0ccc8 Merge the Printers page and spool RFID tags
All checks were successful
build-and-push / docker (push) Successful in 16s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:03:17 +10:00
dlawler489
e98edaa9ed Add a Printers page for any number of printers, and show spools' RFID tags
Printers move out of Settings into their own table and page: each has an
optional nickname (the model is shown when blank), a model from the Bambu
lineup with a line drawing that follows the selection, an AMS type
suggested from the model, and its address, serial and access code. Access
codes are never returned to the browser. The printer set up in Settings is
migrated automatically and its settings rows removed.

Background sync covers every printer. Grams left are filled in only from an
AMS that measures (AMS, AMS 2 Pro, AMS HT), never from an AMS lite.

Spools now show their RFID tag ID, product code and filament code, and say
which printer they're loaded in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:00:43 +10:00
dlawler489
1c19ac565e Merge automatic spools from printer reports
All checks were successful
build-and-push / docker (push) Successful in 14s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:41:31 +10:00
dlawler489
48f564c648 Add spools automatically from what the printer has loaded
Every Bambu spool reports an RFID identity — a spool id and a tag id, both
unique — so a printer's report can now keep the Filament page in step: a new
tagged spool is created, named and coded from the bundled filament list; one
already known is moved to its current slot; one you added by hand before the
printer was connected is adopted rather than duplicated; and one the printer no
longer reports is marked as taken out. It runs on "Save & test connection" and
every ten minutes in the background, still only reading from the printer.

Untagged spools are listed, not stored: without a tag, one spool of a colour is
indistinguishable from the next, so guessing would mean duplicates.

The planning is a pure function, tested against the cases that would cause
duplicates. The database side was exercised separately against a real
Postgres: three successive reports created, moved and unloaded spools as
intended, and a deliberate duplicate was refused by the partial unique index.

The amount left isn't taken from the printer yet: an AMS lite only reports full
or empty, so it's left blank until printers can say which kind of AMS they have.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:39:30 +10:00
dlawler489
99d6bdf06c Merge spool RFID identity and the raw-report diagnostic
All checks were successful
build-and-push / docker (push) Successful in 15s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:31:50 +10:00
dlawler489
88251ccd8a Keep a spool's RFID identity, and add a raw-report diagnostic
The tray reader kept material, colour, profile and amount but dropped the
spool's RFID fields, so the printer test couldn't have shown a filament's
serial even if the printer sent one. It now keeps tray_uuid (the spool),
tag_uid (the tag) and tray_id_name where the firmware provides it, and turns
the all-zero values sent for untagged spools into "none" rather than a fake id.

The printer test also takes ?debug=1, which returns one loaded tray exactly as
the printer sent it, to see which fields a given printer and firmware provide
before designing around them. It's filament data; no credentials are in it.

Also notes that an AMS lite's "remain" is full-or-empty rather than a
measurement, which the next change needs to account for per printer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:26:47 +10:00
dlawler489
7c893e50bd Merge read-only Bambu Lab printer connection
All checks were successful
build-and-push / docker (push) Successful in 31s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:16:36 +10:00
dlawler489
93130967af Connect to a Bambu Lab printer, read-only
Settings → Printer takes the printer's address, serial number and access code
(the code stored as a secret, like the other tokens). "Save & test connection"
connects to the printer's own MQTT broker on port 8883, subscribes to its
reports, asks once for a full report, and listens for a few seconds — then
says what it heard: its state, and the filament in each AMS slot or on the
external spool holder, with how much is left where the printer knows.

Nothing sent to the printer changes what it is doing; the only thing published
is the request for a full report. If the printer is in cloud mode and treats
even that as a write, the test says it received nothing rather than failing
silently. The access code never leaves the server.

Adds mqtt as a runtime dependency — server.js now loads it at startup, so the
lockfile has to travel with this change.

Not verified against a real printer yet: the printer's ports answer on the
network, but no connection has been made with its credentials.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:15:15 +10:00
dlawler489
122f9d53ce Merge any-colour slot assignment order
All checks were successful
build-and-push / docker (push) Successful in 15s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:40:37 +10:00
dlawler489
1581f767fa Fill "any colour" slots last, not first
Found on the live library: setting slot 2 of a four-colour bookmark to "any
colour" made it take the one Jade White spool, leaving slot 4 — which asks for
#FFFFFF — with nothing, and the verdict stuck at "needs 3 colours" when it
should have improved to 2.

The assignment sorts candidate pairs by colour distance and scored a colourless
slot 0, a perfect match, so it always claimed a spool ahead of a slot that
needed that exact colour. Colourless slots now sort last and take whatever is
left over.

Fixed in both the server and the browser copy, which mirror each other, and
pinned by a test built from the reported case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:27:58 +10:00
dlawler489
e8b68e1149 Merge editable filament slots, with an any-colour option
All checks were successful
build-and-push / docker (push) Successful in 16s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:21:04 +10:00
dlawler489
8f3e63585c Let a slot ask for any colour
Some prints don't care about the shade, and some change with the order. Each
slot in the editor now has an "any colour" box: ticking it greys out the swatch
and saves the slot without a colour, which the matcher fills from any spool of
that material. A slot added by hand starts this way, since adding one usually
means you care about the material rather than the shade.

An unparseable colour already became "any colour" rather than being refused;
that stays, and is now covered by tests along with the shapes the checkbox
actually sends — null, empty string, and no colour key at all.

Worth knowing when reading the printable count: ticking "any colour" makes a
model easier to satisfy, so the count now partly reflects your decisions rather
than only what the project files ask for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:19:47 +10:00
dlawler489
eaf1bd93d5 Let the required colours be edited on the model page
The slot list comes from the project file, which is sometimes wrong and
sometimes not what you intend to print. "Edit slots" turns it into fields:
material and colour per slot, with slots you can add or drop. Saved slots are
marked as yours and shown with a "your list" badge, and "Use the project's
list" throws the override away and re-reads the 3MF.

Edits have to outlive the background reader, which is the part that would fail
silently: the reader only picks up models with no status, and reread() now
skips 'edited' rather than nulling everything. Verified on a real model — the
edit survived a reread plus a full worker pass, and the revert restored the
project's own four slots exactly.

Input cleaning moved out of the route into lib/filaments.js so it can be
tested: a slot with no material is dropped, and a colour that isn't a proper
hex becomes "any colour of that material" rather than being rejected.

Not verified: none of this has been seen rendered — the local preview browser
has no session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:15:48 +10:00
dlawler489
c81dc68c0b Merge colour-naming fixes
All checks were successful
build-and-push / docker (push) Successful in 14s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:12:25 +10:00
dlawler489
08c6dcc432 Name a colour after the everyday filament, not the first match
Seventeen PLA filaments are #000000 and eleven are #FFFFFF, so slot 1 of a
bookmark came out as "Aero Black" — true, but not what anyone calls it. Naming
now prefers Basic, then Matte, before specialist types.

Also puts a space between the "none" badge and the nearest-spool hint, which
were running together as "nonenearest: Jade White".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:10:18 +10:00
dlawler489
d6ad49d9cb Merge colour-accurate filament matching and the Bambu filament picker
All checks were successful
build-and-push / docker (push) Successful in 16s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:06:40 +10:00