diff --git a/CHANGELOG.md b/CHANGELOG.md index 0172fb2ab..ab9430971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ All notable changes to Bambuddy will be documented in this file. ### Changed - **Inventory: `/reset-usage` renamed to `/reset-consumed-counter`; UI label is now "Reset counter"** — The old endpoint name implied that calling it would drop `weight_used` to 0; in practice it only stamps `weight_used_baseline = weight_used` so the Inventory page's "Total Consumed" widget (which renders `weight_used - baseline`) reads 0 going forward, while remaining (`label_weight - weight_used`) is preserved. Calling the endpoint via curl and seeing `weight_used` unchanged in the JSON response is confusing — the name didn't describe what the endpoint actually does. New paths: internal `/api/v1/inventory/spools/{id}/reset-consumed-counter` + `/spools/reset-consumed-counter-bulk`, Spoolman-mode `/api/v1/spoolman/inventory/spools/{id}/reset-consumed-counter` + `/spools/reset-consumed-counter-bulk`. **Behaviour is unchanged in both modes**: internal stamps the baseline directly; Spoolman-mode PATCHes upstream `used_weight=0` and the `_map_spoolman_spool` read mapping at `_spoolman_helpers.py:252-268` reconstructs the same `displayed consumed = 0, remaining unchanged` Bambuddy-visible shape — Bambuddy-side parity between modes (per [[feedback_inventory_modes_parity]]) was already in place before this rename and is preserved. The Spoolman-client method `reset_spool_usage` keeps its name because it describes what's sent upstream to Spoolman, which has not been renamed. **Frontend**: `api.resetSpoolUsage` / `bulkResetSpoolUsage` (and Spoolman variants) renamed to `resetSpoolConsumedCounter` / `bulkResetSpoolConsumedCounter`. Button labels switch from "Reset usage to 0" to "Reset counter" / "Reset all counters" — short and unambiguous; tooltips and confirm-modal bodies still spell out the full semantics ("zero the consumed-grams counter; remaining weight is not changed"). **i18n**: 9 keys renamed (`resetUsage*`, `resetAllUsage*`, `usageReset`, `allUsageReset`, `resetUsageFailed` → `resetConsumedCounter*`, `resetAllConsumedCounters*`, `consumedCounterReset`, `allConsumedCountersReset`, `resetConsumedCounterFailed`); real translations shipped in all 10 non-English locales (de / es / fr / it / ja / ko / pt-BR / tr / zh-CN / zh-TW) per the hard-rule against English fallbacks. **Tests**: `test_spool_reset_usage.py` (9 tests, internal mode) and 3 reset-related tests in `test_spoolman_inventory_api.py` updated to hit the new paths; behavioural assertions unchanged. **Breaking change for external API consumers** that already wired `/reset-usage` — no compat shim shipped because the old name actively misled callers; the migration is a one-line URL swap. +### Changed +- **`docker-compose.yml`: bridge-mode warning about the 1001-port FTP passive range + docker-proxy RAM footprint (#1646, reported by @TheFou)** — Reporter on bridge mode (Docker default `userland-proxy: true`) saw ~2000 `docker-proxy` host processes spawn from the commented `"50000-51000:50000-51000"` line, pinning ~3.5 GB of host RAM before they had even logged in for the first time. Linux's host-mode default in the same compose file sidesteps this entirely (zero docker-proxy cost) — the issue only fires when a user forces bridge mode (typically macOS/Windows / Docker Desktop). The 1001-port range itself is load-bearing on the VP server side (`virtual_printer/ftp_server.py:567-574` documents the widening from 100 ports as multi-VP collision-avoidance headroom; reverting would regress that), so the fix is documentation, not code. Added a warning block above the commented FTP-passive line pointing bridge-mode users at `{ "userland-proxy": false }` in `/etc/docker/daemon.json` — the reporter confirmed this clears the issue on their setup. Kernel does NAT directly via iptables/nftables in that mode, no per-port host process needed; only side-effect is that connections originating from 127.0.0.1 on the host itself can't reach the container, which doesn't matter for nearly every Bambuddy install. + ### Fixed - **MakerWorld URL imports into a writable external folder wrote bytes to internal storage, not the NAS (#1645, reported and root-caused by @needo37)** — Reporter linked a writable external SMB folder, selected it as the destination in the MakerWorld import dialog, the import succeeded, the file card appeared in the File Manager under the external folder's view — but `ls` on the NAS turned up nothing, and a `find` across the whole NAS and the container for the original filename matched nothing either. The bytes had landed in Bambuddy's internal `/archive/library/files/.3mf` instead of `/` on the mount. Root cause was the byte-import save helper `save_3mf_bytes_to_library` at `backend/app/api/routes/library.py:422`: it accepts `folder_id` but never loaded the folder or inspected `is_external` / `external_path`, hardcoded the destination to `get_library_files_dir() / `, and left the `LibraryFile` row with `is_external=False`. So the row's `folder_id` pointed at the external folder while its bytes + `is_external` flag both said "managed/internal" — exact same class of bug as #1112 (which got fixed for the multipart-upload and move paths but never applied to the byte-import path). Compounded by the UUID-renamed on-disk copy: searching for the human-readable basename anywhere — NAS or container — never matches. **Fix** is a direct mirror of the multipart-upload path that's done this correctly since #1112: load the target `LibraryFolder` (when `folder_id` is non-None), feed it to the existing `_resolve_upload_destination(target_folder, filename)` helper which already produces `(dest, is_external)` and enforces the 403-read-only / 400-unwritable-or-missing / 409-collision rejections, write the bytes to that destination (real filename for external, UUID for managed), and persist the row via `_stored_file_path(dest, is_external)` + `is_external=is_external`. The route-layer read-only guard at `makerworld.py:256-260` is preserved — it returns the friendlier error before the upstream download burns bandwidth — and `_resolve_upload_destination`'s identical check stays as defence-in-depth for any future caller that skips the route gate. Thumbnails continue to live under the managed `get_library_thumbnails_dir()` regardless of the 3MF's location, matching the upload path. **Tests**: 4 new in `TestImport` (writable external → bytes on mount + `is_external=True` + absolute file_path persisted; read-only external → 403 at route, no download; missing external_path → 400; filename collision → 409 with the pre-existing file's bytes untouched). 21 existing makerworld tests + 72 library-route tests stay green. Ruff clean. diff --git a/docker-compose.yml b/docker-compose.yml index 5b32164ef..232d0f35e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,6 +35,21 @@ services: # - "322:322" # Virtual printer RTSP camera (X1/H2/P2; proxy mode + non-proxy modes with a target printer) # - "2024-2026:2024-2026" # Virtual printer proprietary ports (A1/P1S) # - "50000-51000:50000-51000" # Virtual printer FTP passive data (widened from 50000-50100 for multi-VP headroom) + # + # ⚠️ Bridge-mode + Docker's default userland proxy: the 1001-port FTP + # passive range spawns ~2000 docker-proxy host processes (IPv4+IPv6 + # × 1001 ports), each pinning ~3.5 MB of host RAM, for a ~3.5 GB + # footprint that doesn't show up in `docker stats` because it's + # host-level, not container-level (#1646). Linux's host-mode default + # above sidesteps this entirely. If you genuinely need bridge mode + # (e.g. Docker Desktop on macOS/Windows), set + # { "userland-proxy": false } + # in /etc/docker/daemon.json and restart Docker. Confirmed to clear + # the issue by the reporter; the kernel does NAT directly via + # iptables/nftables, no per-port host process needed. Only side- + # effect is that connections originating from 127.0.0.1 on the host + # itself can't reach the container — fine for nearly every + # Bambuddy install. volumes: - bambuddy_data:/app/data - bambuddy_logs:/app/logs