Commit Graph
1806 Commits
Author SHA1 Message Date
Donkie 016ca6791e Back up to the target without taking file locks on it
Backup folders on network shares often refuse SQLite's file locks (CIFS
without nobrl, NFS without lockd). SQLite reports a refused lock as
SQLITE_BUSY, so every backup to such a folder retried until the 60 second
give-up and blamed a locked database, while the live database was not
locked at all (#1246).

The target is a scratch .pending file that nothing else opens, so open it
with nolock=1. The give-up message now names the live database, since that
is the only file a backup can still wait on.
2026-10-10 14:36:01 +02:00
Donkie bcc1ba1ace Stop a locked database from hanging the server during backup
sqlite3's Connection.backup() retries forever while the source is busy,
calling progress with 0 of 0 pages each time. The backup ran synchronously
on the event loop, so a lock that did not clear froze every request and
spammed "Copied 0 of 0 pages." into the log (#1191).

Run backups in a worker thread, give up after the source has been locked
for 60 seconds, and serialize backups now that they can overlap. Per-page
progress drops to debug.
2026-10-10 14:32:21 +02:00
Donkie 629b8228d8 chore: update sponsors image 2026-10-10 09:15:57 +00:00
Donkie bff2a485e6 Merge pull request #1248 from Donkie/feat/filament-manufacturer-list
Filament and manufacturer lists in the Library
2026-10-10 10:17:09 +02:00
Donkie 7e83451226 Catalog lists: Escape closes toolbar menus, tidier footer, list by id
Escape now closes the Filter, Group and Sort menus when they were opened
with a click, which left focus on their button where the menu never saw
the key.

The pager's page numbers and page-size picker wrap as one, so the
catalog's longer summary line no longer strands the picker on a line of
its own on phones.

"Show as a list" in the manufacturer inspector filters by the
manufacturer's id rather than its name, so a namesake stays out.
2026-10-10 10:02:26 +02:00
Donkie 30928407a7 Catalog lists: every sort and filter, plain #ids
The filament and manufacturer lists now sort by every field of the
filament or manufacturer, their custom fields included, plus spool and
filament counts, and offer the same filters as the spool view. A filter
on the spools themselves leaves out the filaments with none, as it does
there.

/spool/group takes the matching sort keys: filament.<column>,
filament.vendor.<column>, filament.extra.<key>,
filament.vendor.extra.<key> and group.filament_count. Keys it can't
resolve are still ignored.

Rows show the id as #id on the left like spool rows, and the Library
tab menu loses its descriptive sublines.
2026-10-10 10:02:26 +02:00
Donkie 02f5c07c13 Add filament and manufacturer lists to the Library
The Library gets two power-user sub-views, /?view=filaments and
/?view=manufacturers, reached from a menu on the Library tab, the search
panel and the manufacturer inspector. Rows are lists rather than tables so
translated labels and long names never widen them. The spool view is
unchanged.

The group endpoint gains what the lists need, all additive: a
group.vendor_name sort, include_empty for group_by=vendor, filament_count
on vendor groups, an opt-in preview of a group's first spools or
filaments, and X-Total-Spools / X-Total-Remaining-Weight headers.
2026-10-10 10:02:26 +02:00
Donkie 022285d64d Add Argos visual snapshots of client_v2 pages to CI
A screenshots Playwright project seeds a fixed dataset on the fresh CI
database and captures the main pages in light/dark at desktop and phone
widths. The Argos reporter uploads them from CI and posts the diff against
master on the PR. Fork PRs upload tokenless.

Also scope the transparency spec's swatch locator to the inspector header,
it picked the first list row once other spools sorted ahead of its own.
2026-10-10 09:35:51 +02:00
Donkie 6b1e01ecd8 chore: update sponsors image 2026-10-04 09:11:19 +00:00
Donkie 7c4dcc7da7 chore: update sponsors image 2026-10-01 09:38:56 +00:00
Donkie dcb4618399 chore: update sponsors image 2026-09-30 09:11:16 +00:00
Donkie 7a1114f8fc chore: update sponsors image 2026-09-29 09:20:58 +00:00
Donkie 2c15af00b8 Merge pull request #1187 from Donkie/worktree-phone-nfc-auto-navigate
Auto-navigate on tags read by the phone's own NFC
2026-09-27 08:52:59 +02:00
Donkie ac9a32fa23 Merge pull request #1184 from IdefixRC/feat/scanner-camera-switch
Let the QR scanner pick between cameras
2026-09-27 08:48:16 +02:00
Donkie 787d912dec Merge pull request #1194 from Donkie/worktree-qr-custom-template-fields
Resolve label fields in custom QR URL templates
2026-09-27 08:46:52 +02:00
Donkie 8badd51707 Merge remote-tracking branch 'origin/master' into worktree-phone-nfc-auto-navigate 2026-09-27 08:44:40 +02:00
Donkie 53f09eb2bc Use node:crypto for test tag UIDs 2026-09-27 08:44:40 +02:00
Donkie b3d35d751d Merge pull request #1193 from Donkie/worktree-update-indicator-1192
Add update available indicator
2026-09-27 08:43:27 +02:00
Donkie 9f29826588 Resolve label fields in custom QR URL templates
A spool label's custom QR target can now use fields like {filament.id},
so it can open the filament page while still printing spool fields such
as the lot number. Closes #1152.
2026-09-27 08:41:26 +02:00
Donkie f7c53187c1 Test phone NFC reads with a stand-in NDEFReader
Headless Chromium has no Web NFC, so install a small fake of the surface
client_v2 uses (NDEFReader, scan, reading event, nfc permission) and cover
auto-navigate from a phone read, the unknown-tag toast, starting once
permission is granted, and the link dialog's own read.
2026-09-27 08:38:53 +02:00
Donkie 1e9d74cd6e Merge branch 'master' into worktree-update-indicator-1192 2026-09-27 08:38:16 +02:00
Donkie 1a04139c1d Add update check against GitHub releases (#1192)
The server checks the latest GitHub release in the background at startup
and every 12h, logs a warning when a newer version exists, and exposes
latest_version/update_available on /info. The client_v2 footer shows a
link to the new release. Disable with SPOOLMAN_UPDATE_CHECK=FALSE.
2026-09-27 08:32:57 +02:00
Donkie aae2b4df56 ci: skip the duplicate pull_request run for same-repo branches
Now that every branch builds on push, opening a PR from one ran the whole
workflow twice for the same commit.

Concurrency cannot dedupe these: push and pull_request carry different refs, and
forcing them into one group only races them, killing whichever run started
first. That is the wrong outcome roughly half the time, since publish-images
runs on push and not on pull_request, so the branch image would vanish.

Guard the three jobs that have no `needs` instead. A skipped dependency skips its
dependents, so this cascades to all twelve. Forks are unaffected: a fork's push
fires no event in this repo, so its pull_request run is the only one there is.
2026-09-27 08:32:44 +02:00
Donkie e1ad9feb30 ci: build every branch and publish it to GHCR under the branch name
Branch pushes matched no trigger, so an unmerged fix could not be handed to a
bug reporter as an image. Push now covers all branches except weblate (constant
locale-only commits) and dependabot (arrives as PRs anyway).

publish-images was already gated only on "not a pull request", so it picks
branch pushes up for free; it just had no tag to publish them under. Adding
type=ref,event=branch to the GHCR metadata gives one, skipping master since that
is already :edge. Docker Hub deliberately keeps only edge and tagged releases,
so users browsing it do not see half-finished branches.

Concurrency now cancels superseded runs on any ref except master and tags, which
publish images and must not be interrupted mid-manifest.

Note: once a branch has an open PR, push and pull_request both fire and it runs
twice.
2026-09-27 08:32:44 +02:00
github-actions[bot] 5b93b3b37e Merge pull request #1190 from Donkie/weblate
Translations update
2026-09-26 14:04:04 +00:00
github-actions[bot] bd4b9351aa Merge pull request #1189 from Donkie/weblate
Translations update
2026-09-26 13:57:54 +00:00
jernejp21 958d5bd9ee Translated using Weblate (Slovenian)
Currently translated at 100.0% (293 of 293 strings)

Co-authored-by: jernejp21 <jernejp21@tuta.com>
Translate-URL: https://hosted.weblate.org/projects/spoolman/web-client/sl/
Translation: Spoolman/Web Client LEGACY
2026-09-26 13:57:53 +00:00
github-actions[bot] c9b1215cb6 Merge pull request #1188 from Donkie/weblatev2
Translations update from Hosted Weblate
2026-09-26 13:57:39 +00:00
jernejp21 586299aaa6 Translated using Weblate (Slovenian)
Currently translated at 22.3% (136 of 609 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/sl/
2026-09-26 13:51:41 +00:00
jernejp21 7e98987b87 Translated using Weblate (Slovenian)
Currently translated at 100.0% (293 of 293 strings)

Co-authored-by: jernejp21 <jernejp21@tuta.com>
Translate-URL: https://hosted.weblate.org/projects/spoolman/web-client/sl/
Translation: Spoolman/Web Client LEGACY
2026-09-26 13:51:35 +00:00
RRH74 ba62681856 Translated using Weblate (Spanish)
Currently translated at 22.6% (138 of 609 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/es/
2026-09-26 13:51:32 +00:00
IdefixRCandClaude Opus 5.5 05e4da7a10 Pick the scanner camera from a list instead of cycling
Review feedback on #1184. A native select replaces the cycle button, still
shown only when more than one camera is listed. Picking directly suits
desktops, where webcams have real names, and on Android the select opens the
system's full-screen picker. It also removes a trap in the cycle: qr-scanner
opens a default camera when the requested one fails, and cycling on from that
default could never reach the cameras after the failing one. Every camera is
now one choice away, and the select shows the camera actually streaming.

Cameras with no label are listed as "Camera 1", "Camera 2" and so on. The
list now comes from enumerateDevices() directly, because qr-scanner's
listCameras() fills gaps with an untranslated "Default Camera" / "Camera N"
that can't be told apart from a real device name.

A failed switch now shows the same classifyStartFailure() message as a failed
start, instead of leaving the stage black with only a console error. The
picker stays up so another camera can be tried. After a failed open the
scanner is stopped, and setCamera() only restarts a running scanner (or does
nothing for the same id), so a retry starts it explicitly.

The caption and its n/N position are gone, since the select names the
current camera itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:48:46 +08:00
Donkie 0543b9145b Auto-navigate on tags read by the phone's own NFC
Auto-navigate only listened to the scan relay, so a tag tapped against an
Android phone running the client did nothing outside the link-tag dialog.
While auto-navigate is on, also run a background Web NFC reader and feed
its reads through the same handler, looking the holder up by UID.
2026-09-25 20:29:26 +02:00
Donkie 25d25e3701 Merge pull request #1186 from Donkie/worktree-armv7-build-speed
Speed up armv7 image builds when the layer cache misses
2026-09-25 16:11:38 +02:00
Donkie a353983f85 Speed up armv7 image builds on layer-cache misses
Persist uv's cache of source-built wheels (asyncpg, greenlet, psycopg2,
cffi, ...) across CI runs via actions/cache + buildkit-cache-dance, so a
lockfile or base-image change no longer recompiles everything under QEMU
(~11 min -> ~3 min). Move the greenlet patchelf step into the cached
dependency layer so source-only builds skip an apt-get under QEMU, and
drop python3-dev, which only pulled in Debian's unrelated python3.11.
2026-09-25 16:04:21 +02:00
Donkie bed3f14ee7 Bump version to 0.27.0 v0.27.0 2026-09-25 13:35:18 +00:00
Donkie 27ea256e2d Merge pull request #1078 from romasku/fix/sqlite-unicode-search
fix: make SQLite searches Unicode case-insensitive
2026-09-25 15:33:02 +02:00
Donkie 7b82f2afb2 Merge pull request #1164 from Niko11111/fix-1117-vendor-tare-on-measure
Resolve the empty spool weight up to the vendor when measuring
2026-09-25 15:24:21 +02:00
Donkie 45cc8246d3 Merge pull request #1185 from Donkie/fix-1169-location-comma
fix(api): keep quoted string filter terms whole when they contain commas
2026-09-25 15:23:57 +02:00
Donkie 0c4caa527e fix(api): keep quoted string filter terms whole when they contain commas
String filters split their value on every comma before checking for quotes, so an exact
match like location="Top shelf, Rack 3" became two fuzzy terms that matched nothing.
The dashboard and library filters always send quoted values, so a location with a comma
showed as empty. Split with a quote-aware helper shared by the built-in string filters
and extra-field filters. Unquoted values split exactly as before.

Fixes #1169
2026-09-25 15:14:26 +02:00
Donkie 2ab68cdff9 Merge pull request #1168 from Donkie/dependabot/uv/anyio-4.14.2
build(deps): bump anyio from 4.14.1 to 4.14.2
2026-09-25 15:10:29 +02:00
Donkie fc6aa55d27 fix(labels): let the print dialog pick sheet orientation, clamp migrated margins
Two more problems from #1149.

Sheet mode set @page size to the paper's exact dimensions. A 62x34 page makes
the browser send it as landscape, and Chromium hides the orientation picker
when the page CSS sets a size. Label printer drivers disagree on which way
round a roll is: a Brother QL needed landscape, a Niimbot B1 needed portrait,
so any fixed choice prints rotated for someone. Sheet mode now uses size: auto
like v1 did, and the page is still laid out in mm. Label mode keeps its exact
size.

The v1 migration derived the label size from the raw margins. v1 users paired
-20mm margins with a 1.5mm printer margin to pull an oversized cell back onto a
62x34 page, which migrated as a 102x74 label. Margins are now clamped at
migration; the safe-zone alone gives the same content inset (max(safe, margin)
in both versions), and the label comes out at the paper size.
2026-09-25 15:08:36 +02:00
Donkie 92e093b16d fix(labels): keep sheet-printed labels at the right size and shape
Two bugs in the sheet-mode safe-zone inset, both reported in #1149.

The label raster has a fixed aspect ratio, but labelImg sized its width and
height independently, so a safe-zone squashed the label (1.5mm top/bottom on a
34mm label is a 9% vertical squeeze). v1 padded a box of reflowing HTML, where
that was harmless; a raster has to scale uniformly, so the inset box now gets
object-fit: contain.

Negative page margins made it much worse. v1 accepted them and used them to size
the grid cells; in v2 the label size comes from the design and the margin only
positions an absolutely placed grid inside an overflow:hidden page, so a negative
margin can only clip. It also inflated overSafe (safe - margin), which turned a
1.5mm safe-zone into a 21.5mm inset: at margin -20 the label rendered 22x12mm in
the corner of a 62x34mm page, and sheetGrid invented a second row off the paper.

Margins are now clamped to zero on read, so existing presets carrying v1's
negative values are fixed without the user editing them, and the margin inputs
refuse new negative values.
2026-09-25 15:08:36 +02:00
Donkie 55d11d0dc4 Merge pull request #1166 from Donkie/dependabot/npm_and_yarn/client_v2/devalue-5.9.2
build(deps): bump devalue from 5.9.0 to 5.9.2 in /client_v2
2026-09-25 15:08:27 +02:00
IdefixRCandClaude Opus 5.5 6c5d03d4ec Let the QR scanner switch between cameras
The scanner asks for the 'environment' camera and takes whichever rear camera
the browser hands it. On a phone with several lenses that can be the
telephoto: an Oppo X9 Ultra lists its rear cameras as camera 5, 4, 3, 2, 0 and
Chrome opens the zoom lens, which only fits a label in frame from across the
room. On a desktop the same thing happens with a virtual webcam, and once the
permission is saved the browser never offers the choice again (#693).

When more than one camera is listed, a switch button over the video steps to
the next one and wraps around. Each switch briefly shows the camera's name and
position, since phone lenses are only told apart by numbers. The camera the
user settles on is remembered per browser and opened directly next time.

A remembered camera that is no longer listed falls back to 'environment'.
That is checked before starting, because qr-scanner retries an unavailable
deviceId with no constraint at all, which can land on the front camera.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 19:46:02 +08:00
github-actions[bot] 058490a3c6 Merge pull request #1183 from Donkie/weblatev2
Translations update from Hosted Weblate
2026-09-24 10:57:40 +00:00
Julia 9131454aa2 Translated using Weblate (Polish)
Currently translated at 100.0% (609 of 609 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/pl/
2026-09-24 10:51:34 +00:00
github-actions[bot] c73bce4301 Merge pull request #1182 from Donkie/weblatev2
Translations update from Hosted Weblate
2026-09-22 23:56:58 +00:00
Юрій «Zloj Kashtan» Скоріянтов 1c0f668c16 Translated using Weblate (Ukrainian)
Currently translated at 22.4% (137 of 609 strings)

Translation: Spoolman/Spoolman Web UI v2
Translate-URL: https://hosted.weblate.org/projects/spoolman/spoolman-web-ui-v2/uk/
2026-09-23 01:51:31 +02:00
github-actions[bot] de81990a29 Merge pull request #1177 from Donkie/weblatev2
Translations update from Hosted Weblate
2026-09-21 18:04:23 +00:00