Commit Graph
65 Commits
Author SHA1 Message Date
Ed 14a6d294f4 Merge pull request #1272 from EdwardChamberlain/feat-page-icons
[Feature] Update page icons for consistent UI style
2026-05-12 14:01:37 +02:00
maziggy ba3dc613d1 Changed app defaults 2026-05-10 11:15:47 +02:00
maziggy 32b5c42dfb fix(permissions): hide MakerWorld nav entry from users without makerworld:view (#1175)
Backend routes were already gated on makerworld:view, the permission
  was granted to admin + standard-user role defaults, and the frontend
  Permission type union already included 'makerworld:view' — but the
  sidebar's hand-maintained navPermissions map in Layout.tsx had no
  entry for `makerworld`. So `isHidden('makerworld')` always returned
  false, the entry rendered for every authenticated user regardless
  of group permissions, and the only way the user found out they
  couldn't use it was by clicking and getting 403'd by every API call.

  Fix is two lines:

  - Layout.tsx: add `makerworld: 'makerworld:view'` to navPermissions,
    matching every other sidebar entry's gating shape.
  - App.tsx: wrap the /makerworld route in PermissionRoute for defence
    in depth, so a user who knows the URL can no longer reach the page
    directly. Same pattern already used by settings, groups/new, and
    groups/:id/edit two lines below.

  Two new Layout tests pin the contract: with auth enabled and a user
  lacking makerworld:view, the sidebar <a href="/makerworld"> link is
  absent while other links still render; with the permission granted,
  the link renders.
2026-05-01 08:31:19 +02:00
maziggy 4aea4be2bd feat(updates): detect HA Supervisor addon and defer update UI to it (#1167)
Bambuddy already supports running as a Home Assistant addon
  (HA_URL/HA_TOKEN env-var integration since #283, community addon at
  hobbypunk90/homeassistant-addon-bambuddy), but the update UI was
  oblivious to it: HA addon users saw the in-app "Update available"
  banner and, on Settings, the docker-compose snippet — neither of
  which they can act on, since the HA Supervisor owns the addon
  lifecycle.

  Detection uses the SUPERVISOR_TOKEN env var that HA Supervisor
  injects into every addon container; no other environment sets it,
  so the check has zero false-positive surface.

  Backend:
    - new _is_ha_addon() helper in routes/updates.py
    - /updates/check now returns is_ha_addon: bool and extends
      update_method to 'git' | 'docker' | 'ha_addon'
    - /updates/apply checks HA before Docker (HA addons ARE Docker
      containers, so checking docker first would mis-classify) and
      returns an HA-specific message that points to Settings →
      Add-ons → Bambuddy in HA
    - response keeps is_docker: true alongside is_ha_addon: true so
      older frontend bundles still hit a managed-deployment branch
      instead of rendering an Install button that can't work

  Frontend:
    - SettingsPage update card branches on is_ha_addon BEFORE
      is_docker; HA users get a Supervisor-targeted message instead
      of the docker-compose snippet
    - Layout update banner is suppressed for HA addons — HA
      Supervisor surfaces its own update notification natively, so
      Bambuddy's banner would be duplicate noise linking to a page
      that just says "update via HA"
    - Plain Docker deployments are unaffected

  i18n: settings.updateViaHomeAssistant added to all 8 locales with
  full native translations.

  Tests: 3 backend unit tests for _is_ha_addon (present, absent,
  empty-string treated as unset), 3 backend integration tests
  (HA-precedes-Docker rejection on apply; HA branch on check; plain
  Docker branch on check), 2 SettingsPage tests pinning the
  mutually-exclusive UI rendering, 2 Layout tests pinning banner
  suppression for HA and retention for plain Docker.
2026-05-01 08:01:19 +02:00
MartinNYHC 5da403ba0c Feature/makerworld (#1099)
* feat(makerworld): URL-paste import and print for MakerWorld models

  Add a dedicated /makerworld sidebar page where users paste a MakerWorld
  model URL and get the full plate list + one-click "Import to Library" or
  "Print Now". Closes the workflow gap that kept LAN-only users on the
  Bambu Handy app solely for MakerWorld download-and-send.

  The authenticated tier reuses the existing Bambu Cloud token that
  Bambuddy already stores for firmware checks and slicer settings --
  MakerWorld shares the same auth backend, so the same JWT works there.
  No separate OAuth flow, no companion browser extension, no credential
  hijack. Anonymous users can still paste a URL and see model metadata;
  the 3MF download itself requires the Cloud login.

  Print Now hands off to the existing PrintModal (plate picker + AMS
  mapping + dispatch) so multi-filament models work via the same code
  path as library-file prints. Imported 3MFs are stored through a new
  shared save_3mf_bytes_to_library() helper so the multipart upload
  route and the MakerWorld import route don't duplicate 3MF parsing +
  thumbnail extraction logic.

  LibraryFile gains indexed source_type + source_url columns. Re-pasting
  a URL for a model already in the library returns the existing row
  instead of re-downloading -- dedupe is by canonicalised URL, not SHA256,
  because MakerWorld's download URLs are signed and change per request.

  Thumbnail proxy (/makerworld/thumbnail) hot-links through the backend
  instead of directly to makerworld.bblmw.com -- the SPA's img-src CSP
  stays strict and users' IPs don't hit MakerWorld's CDN logs. The
  endpoint is intentionally unauthenticated since <img> tags can't carry
  a Bearer token; SSRF-guarded by a CDN host allowlist so it can't be
  used as a generic proxy.

  Search and browse-catalogue are explicitly out of scope. The public
  design/search endpoint returns empty results from server-originated
  requests (likely needs csrf/session state reproducible only from a
  real browser), and the __NEXT_DATA__ HTML fallback is blocked by
  Cloudflare. URL-paste covers the realistic discovery pattern (Reddit /
  YouTube / shared links).

  Headers match kloshi-io/makerworld-api-reverse's production-tested set
  (User-Agent: 3d-printing-service/1.0, x-bbl-* client identifiers,
  Referer). The /instance/{id}/f3mf call includes ?type=download which
  community userscripts use to signal legitimate download intent. 418
  responses (MakerWorld's CAPTCHA gate) retry once with backoff and then
  surface a clear actionable error with an "Open on MakerWorld" fallback
  link; we never try to evade bot detection.

  Permissions: new makerworld:view (browse metadata, view thumbnails) and
  makerworld:import (save 3MFs to library). Administrators and Operators
  get both; Viewers get view-only. Migration grants these to existing
  groups based on whether they already have library:upload / library:read.

  Disclaimer in the UI and wiki page mirrors kloshi's framing: not
  affiliated with or endorsed by MakerWorld or Bambu Lab, interoperability
  only, not intended to circumvent access controls.

  Tests: 30 backend (service + routes) + 4 frontend. Full backend suite
  (1931 tests) clean. Frontend build clean.

* feat(makerworld): ship working URL-paste import via api.bambulab.com iot-service

  The MakerWorld integration shipped in 0.2.4b1 dev was broken for most
  public models: the makerworld.com/design-service path returns "Please
  log in to download models" even with a valid Bambu Cloud bearer,
  because it's cookie-gated behind Cloudflare. Published reverse-
  engineering projects work around this by pasting browser cookies; we
  route around it entirely by using the api.bambulab.com/iot-service
  endpoint (documented by Pr0zak/YASTL#51), which accepts the same
  bearer Bambuddy already has and returns a presigned S3 URL.

  Working flow:
    GET api.bambulab.com/v1/design-service/design/{id}  → metadata
    GET api.bambulab.com/v1/iot-service/api/user/profile/{pid}?model_id=<str>
         Authorization: Bearer {cloud_token}             → signed S3 URL
    urllib.request (no redirects, no query re-encoding)  → bytes

  Notes on each step:
    - The model_id query param is the alphanumeric string from the
      design response (e.g. US2bb73b106683e5), NOT the integer designId
      from the /models/{N} URL. The import route fetches design metadata
      first to get it.
    - S3 presigned URLs MUST be fetched with urllib (not httpx/curl_cffi)
      because the signature is computed over exact query-string bytes;
      any normalising encoder breaks it with SignatureDoesNotMatch 400s
      (YASTL#52 hit the same issue). Wrapped in a no-redirect opener so
      the .amazonaws.com host allowlist guarantee isn't bypassed by a
      302 elsewhere.
    - The canonical source_url now includes profile_id so different
      plates of the same model get distinct library entries. Older rows
      from dev builds keep the model-level URL; the resolve endpoint's
      "already imported" check LIKEs both shapes.

  UI rebuild:
    - Per-plate Save + Save & Slice in Bambu Studio / OrcaSlicer (the
      plate is unsliced source, so "Print Now" was misleading and is
      replaced by an explicit slicer hand-off).
    - Import all plates with sequential progress.
    - Folder picker (default: auto-created top-level "MakerWorld"
      folder, created on first import, folder tree invalidated so
      File Manager shows it immediately).
    - Image gallery per plate with keyboard-navigable lightbox.
    - Recent imports sidebar (sticky on lg+, vertical list with
      jump-to-library / slicer / open-on-makerworld icons).
    - Inline follow-up actions on imported plate rows so the user
      doesn't scroll back to a top-of-page card.
    - Per-plate delete via the standard ConfirmModal (no window.confirm).
    - Elapsed-time + phase label during import so the 10-30s synchronous
      POST doesn't feel frozen.
    - URL-change detection drops the preview when the pasted URL
      diverges from the resolved one.

  Security hardening (found in review):
    - DOMPurify.sanitize on the MakerWorld HTML summary before
      dangerouslySetInnerHTML (user-authored content).
    - <img> tags in that HTML routed through the thumbnail proxy so
      the SPA's img-src 'self' data: blob: CSP isn't widened.
    - /makerworld/thumbnail uses follow_redirects=False (the host
      allowlist only covers the initial URL).
    - 3MF CDN fetch strips the bearer (signed URL is the credential).
    - S3 fetch uses a no-op HTTPRedirectHandler for the same reason.
    - Upstream filename is os.path.basename'd before persisting.

  Tests: 46 backend service unit tests, 19 route tests, 12 frontend
  tests — all passing. All user-facing strings localised across the
  8 UI languages.

* - frontend/src/App.tsx — removed the 3 stale <AdminRoute> lines (kept the 3 <PermissionRoute> equivalents). TSC + Vite both clean.
  - backend/tests/integration/test_auth_api.py — added # pragma: allowlist secret + # noqa: S106 on the test fixture line that GitGuardian flagged.
2026-04-23 14:10:14 +02:00
maziggy c44b62195a refactor(gcode-viewer): archive-scoped previews, bed from capabilities, plate picker
Reshapes the embedded PrettyGCode viewer (landed in #963) into a focused
  archive-preview tool, matching Bambuddy's data model instead of the
  OctoPrint-style "connected-printer + library file picker" flow it shipped
  with. Reached only from the Archives page 3D-preview button; URL
  /gcode-viewer?archive=<id>[&plate=<N>].

  Backend:
  - /archives/{id}/gcode accepts ?plate=N and resolves the filename by
    parsing the suffix as int, so zero-padded names like plate_01.gcode
    are found when the plates endpoint reports index 1.
  - /archives/{id}/plates gains top-level has_gcode: bool. Source-only
    3MFs (PNG/JSON fallback path) surface the flag so the frontend can
    skip the picker instead of sending the user into a dead viewer.
  - printer_state_to_dict injects name + model into every WS snapshot so
    consumers render proper labels on the initial tick without racing a
    separate /printers fetch.
  - /gcode-viewer (no trailing slash) dropped from the backend so reloads
    fall through to the SPA catch-all and keep the layout shell; only
    /gcode-viewer/ (trailing slash) and /gcode-viewer/<path> remain for
    the iframe + static assets.

  Frontend:
  - PlatePickerModal shown only for multi-plate archives with sliced
    gcode, grid layout with thumbnails matching the Re-print modal.
  - Source-only archives show a noGcode toast instead of the empty
    viewer.
  - ArchivesPage navigate path swapped to /gcode-viewer?archive=<id> with
    no trailing slash; GCodeViewerPage iframe forwards
    window.location.search so the archive reference survives both the
    initial navigate and a full-page reload.
  - Viewer iframe's auth path: fetch intercept injects Bearer; a 401
    redirects to / so the SPA handles login.

  Viewer adapter:
  - Stripped the printer selector, WebSocket subscription, library file
    picker, tryAutoLoadPrintingFile, BAMBU_BED_SIZES, and updatePrinter-
    Selector. The viewer no longer observes live printer state.
  - Bed size derived from /archives/{id}/capabilities.build_volume
    (extracted from the 3MF's printable_area/printable_height), so H2D,
    H-family, and any future printer render on the correct bed without
    a hardcoded map.
  - loadArchiveById accepts a plate param; fetch intercept rewrites
    __bambuddy_archive_<id>[_plate<N>] to /archives/<id>/gcode[?plate=N].

  Nav + locale cleanup:
  - Sidebar "GCode Viewer" nav entry removed (viewer is archive-scoped
    now, not a destination page).
  - 32 orphaned gcodeViewer locale keys deleted across all 8 locales.
  - platePicker.{title, hint, plateLabel, objectCount, noGcode} keys
    added in all 8 locales.

  ArchivesPage: the now-unreachable ModelViewerModal render paths + its
  showViewer state removed. ModelViewerModal itself stays — File Manager
  still uses it for library file previews (plate picker + .3mf 3D model).

  pre-commit:
  - gcode_viewer/ excluded from trailing-whitespace + end-of-file-fixer
    so vendored third-party JS libs don't drift away from upstream.

  Incidental sweeps picked up by pre-commit and kept (unrelated but
  benign):
  - NotificationsPage.tsx: single trailing-whitespace line removed.
  - spoolbuddy/scripts/pn5180_diag.py: dead `import gpiod` dropped —
    the pn5180 driver module imported at line 27 does its own
    `import gpiod` and `gpiod.Chip()` calls, so the diag script's
    top-level import was never referenced.

  Tests:
  - 6 new cases in test_gcode_viewer.py for the backend plate / has_gcode
    behaviour (plate=N resolution, zero-padded filenames, missing-plate
    404, no-plate fallback, plate=0 rejection, has_gcode true/false).
  - 3 new cases in test_printer_manager.py for name/model WS injection.
  - PlatePickerModal.test.tsx — 6 frontend cases covering render,
    plate-name composition, onSelect payload, backdrop close, and
    thumbnail fallback.
2026-04-22 13:03:09 +02:00
Nathen Fredrick 3adce435ee feat: add embedded GCode viewer (#963)
* feat: add embedded GCode viewer

Adds PrettyGCode as a built-in GCode visualiser embedded directly in the
Bambuddy layout, so users can preview and inspect GCode files without
leaving the dashboard.
2026-04-22 11:14:42 +02:00
maziggy 597d961b0c fix(ui): Change Password modal leaked password affordance onto Printers search field
Opening the sidebar's Change Password modal while on the Printers page
  caused the "Search printers" input to render as a masked password field
  and stay that way after closing the modal.

  Root cause: the modal had three type=password inputs but no accompanying
  username anchor, so password-manager extensions (1Password, Bitwarden,
  browser built-ins) hunted the DOM for a matching text input and latched
  onto the unlabelled Printers-page search bar.

  - Layout.tsx: add hidden autocomplete=username anchor at the top of the
    Change Password modal form. Also ensures saved new passwords are
    correctly keyed to the logged-in user.
  - PrintersPage.tsx: harden the search input with type=search,
    name=printer-search, autoComplete=off, data-1p-ignore, data-lpignore
    so heuristic autofill skips it regardless.
2026-04-21 17:22:34 +02:00
maziggy de7fff0be4 fix: persist plate-clear gate so Auto Off power cycles can't bypass the queue confirmation (#961)
With Auto Off enabled and another job queued, the smart plug cut power when a
  print finished and immediately re-powered the printer because the scheduler
  saw pending items. The printer booted fresh into IDLE and the next job
  auto-dispatched, bypassing the "Clear Plate & Start Next" confirmation.

  Root cause: the plate-clear gate lived only in PrinterManager._plate_cleared
  (in-memory set) and _is_printer_idle treated IDLE as unconditionally idle. On
  power cycle the in-memory flag was lost and the IDLE-on-boot state skipped
  the gate entirely.

  Fix:
  - Replace the in-memory flag with an awaiting_plate_clear column on the
    printers table, rehydrated into the PrinterManager at startup.
  - Set the flag in on_print_complete for completed/failed prints (not user
    cancellations); clear it on ack and on scheduler dispatch.
  - _is_printer_idle now short-circuits to not-idle whenever require_plate_clear
    is on and the flag is set, regardless of the currently reported state —
    so the gate holds through power cycles, Bambuddy restarts, and the printer
    booting back into IDLE.
  - /printers/{id}/clear-plate no longer requires the printer to report
    FINISH/FAILED; it accepts the ack whenever the flag is raised.
  - Frontend widgets (PrinterQueueWidget, Layout, BulkPrinterToolbar) gate on
    the flag rather than reported state.

  Tests: added regression tests for IDLE+awaiting=True (the #961 case) and
  full DB round-trip tests for the persistence layer.
2026-04-13 09:12:12 +02:00
maziggy 99c193b535 refactor(colors): color_catalog is the single source of truth (#857)
The Printer tab AMS popup and spool auto-provisioner resolved color
  names from hardcoded tray_id_name tables with a suffix-code fallback —
  and suffix codes like "R1" are not globally unique across material
  families. A17-R1 (PLA Translucent Cherry Pink) fell through the
  fallback and resolved to "Scarlet Red" (A01-R1, PLA Matte), baking
  the wrong name into auto-created inventory spools.

  The fix removes the hardcoded tables entirely. Backend resolves color
  names via the existing color_catalog table by hex; frontend fetches a
  compact {hex: name} map once per session via a new
  GET /inventory/colors/map endpoint (auth-gated but not on
  inventory:read — read-only views need it too) and stores it in a
  ColorCatalogProvider context. A useSyncExternalStore hook cascades a
  re-render into pages mounted before the fetch completes so they
  refresh from HSL-fallback names once the catalog loads.

  Existing auto-provisioned spools keep their stored names; only new
  provisioning and live display benefit. Co-Authored-By is intentionally
  omitted here per project convention — set it via git config if needed.
2026-04-11 12:10:45 +02:00
maziggy d65d440cfb Fix external sidebar link icon missing when auth enabled (#878)
The sidebar <img> tag in Layout.tsx fetched custom external-link icons
  via a raw /api/v1/external-links/{id}/icon URL. That endpoint is
  protected by the shared camera-stream token (passed as ?token=xxx
  because <img> tags cannot send Authorization headers), so the request
  came back 401 with the "Valid camera stream token required" message.

  The edit dialog already routed through api.getExternalLinkIconUrl(),
  which wraps the URL via withStreamToken(); the sidebar now does the
  same in both the open-in-new-tab and NavLink branches.
2026-04-10 09:56:56 +02:00
maziggy f228a8b549 Fix sidebar bottom icons cut off when smart plugs enabled (#862) 2026-04-01 09:17:03 +02:00
Thomas Rambach 2cac7d0172 Feature: Admin Set Default Nav-Menu Order (#761)
Feature: Admin Set Default Nav-Menu Order (#761)
2026-03-21 09:30:18 +01:00
maziggy 7ab9b3e39f Fix mobile sidebar scroll, lint errors, and add tests for (#724)
- Fix mobile sidebar not scrollable when nav items exceed viewport
2026-03-17 12:31:11 +01:00
Thomas Rambach 9562d66b6b Feature: Advanced Authentication User Email Notifications (#693)
Feature: Advanced Authentication User Email Notifications (#693)
2026-03-17 12:01:18 +01:00
maziggy 150eaffe1c Fix bug report bubble overlapping toast notifications
Move the toast container from bottom-4 to bottom-20 so toast
  notifications and upload progress stack above the bug report bubble
  instead of rendering on top of each other.
2026-03-05 09:44:54 +01:00
maziggy 058f74a7da Add in-app bug reporting with relay, debug log collection, and privacy controls
Floating bug report button submits issues via bambuddy.cool relay (no GitHub
  token needed locally). Collects 30s debug logs with printer push_all, sanitizes
  all sensitive data, uploads logs as files to GitHub. Screenshot upload/paste/drag
  with JPEG compression. Translated into all 7 languages. Includes 21 tests.
2026-03-04 13:33:19 +01:00
maziggy 65c904b231 Fix sidebar navigation not respecting user permissions
Sidebar nav items (Archives, Queue, Stats, Profiles, Maintenance,
  Projects, Inventory, Files) were visible to all users regardless of
  role permissions — only Settings was gated. Now each item is hidden
  when the user lacks the corresponding read permission. Printers
  remains always visible as the home page.

  Also adds missing inventory:read|create|update|delete to the frontend
  Permission type (existed in backend but was absent from the frontend
  type definition).
2026-02-28 13:07:54 +01:00
maziggy ed36eafbec Fix timestamps off by timezone offset in non-UTC containers (#504)
All backend timestamps used datetime.now() (server local time) or the
deprecated datetime.utcnow(). The frontend's parseUTCDate() assumes
timestamps without timezone indicators are UTC and appends 'Z', so
stored timestamps were off by the timezone offset when the container's
timezone wasn't UTC.

Backend: replaced datetime.now() and datetime.utcnow() with
datetime.now(timezone.utc) across 16 files (~80 call sites) for all
database fields and DB comparisons. Cosmetic timestamps (filenames,
user-facing local time formatting) intentionally left as local time.

Frontend: replaced 13 new Date(backendTimestamp) calls with
parseUTCDate() across 8 files to correctly interpret UTC timestamps.
2026-02-24 08:18:02 +01:00
maziggy 974988c4f1 Fix created admin users not seeing settings button in sidebar (#503)
The sidebar hid Settings based on hardcoded role === 'user' instead of
the settings:read permission, and login set user state directly from the
response instead of re-fetching full auth status with permissions.
2026-02-24 07:52:41 +01:00
maziggy 3c72f7cb22 Show inventory sidebar item for both Spoolman and internal inventory
Previously the Inventory sidebar item was hidden when Spoolman was
enabled. Now it always shows — when Spoolman is active, /inventory
embeds the Spoolman web UI in the content area via iframe; when
disabled, it renders the internal inventory page as before.
2026-02-23 08:23:35 +01:00
maziggy 77677cb07f Add clear-plate dot indicator on sidebar Printers icon
When a printer has pending queue items and is in FINISH/FAILED state
awaiting plate clearing, a yellow dot now appears on the Printers
sidebar icon. Uses useQueries to check statuses of printers with
pending queue items from the existing WebSocket-warmed React Query
cache — no new backend endpoints or additional polling needed.
2026-02-23 08:13:29 +01:00
maziggy 0e87c377e6 Add developer LAN mode detection and warning banner
Parse the MQTT "fun" field bit 0x20000000 to detect whether connected
printers have Developer LAN Mode enabled. Show a persistent orange
warning banner when any printer lacks it, since newer firmware silently
rejects MQTT write commands without developer mode.

- Parse fun field into developer_mode on PrinterState
- Add /printers/developer-mode-warnings lightweight polling endpoint
- Include developer_mode in printer status API and support bundle
- Orange banner with affected printer names and wiki link
- Translations for all 6 locales (en, de, fr, it, ja, pt-BR)
- 7 backend + 4 frontend tests
2026-02-21 12:06:16 +01:00
Matteo Parenti cf5d403c26 Merge branch 'feature/mobile_view_fix' of https://github.com/Keybored02/bambuddy into feature/mobile_view_fix 2026-02-15 14:24:58 +01:00
Matteo Parenti 4ff3e2a60f Update responsive breakpoints for various components, implement useIsSidebarCompact hook 2026-02-15 14:24:50 +01:00
maziggy aa9db501ec Changed the inventory icon from Package (box) to Disc3 (spool/reel shape) 2026-02-15 13:24:00 +01:00
MartinNYHC 44df253c67 Merge branch '0.2.0b' into feature/inventory 2026-02-12 16:38:03 +01:00
maziggy aa87e5598b Add clear plate confirmation between queued prints
The print scheduler previously treated FINISH/FAILED as idle states
and would auto-start the next queued print before the user cleared
the build plate. Now requires explicit user confirmation via a
"Clear Plate & Start Next" button on the printer card. Uses an
in-memory plate-cleared flag — no MQTT command needed since the
scheduler's start_print overrides the printer state.
2026-02-12 08:45:05 +01:00
maziggy 1b5683b3a3 Add "Open in new tab" toggle for external sidebar links (#338)
External links behind reverse proxies (Traefik, nginx) block iframe
embedding via X-Frame-Options/CSP headers. Add a per-link boolean
toggle so users can choose between iframe (default) and new-tab
behavior. Keyboard shortcuts also respect the setting.
2026-02-12 07:51:55 +01:00
maziggy ec82092bc7 Sync 2026-02-12 07:07:50 +01:00
maziggy 30fba33dcf Fix sidebar link custom icons displaying with inverted colors (#308)
CSS invert() filter was applied to all custom uploaded icons in dark
mode, causing full-color logos to render with wrong colors. Removed
the invert filter from custom icon <img> tags in Layout.tsx and
AddExternalLinkModal.tsx. Preset Lucide icons are unaffected.
2026-02-09 08:35:07 +01:00
maziggy d950b48023 Only show plate detection popup to users with control permission
The plate detection alert popup (shown when objects are detected on
build plate and print is paused) was visible to all users. Now it
only shows to users who have the printers:control permission.

- Added hasPermission('printers:control') check before showing alert
- When auth disabled: all users see it (backward compatible)
- When auth enabled: only users with printers:control permission see it

Addresses #244
2026-02-05 08:21:55 +01:00
maziggy 71def0a9dc Added full i8n localization support. Available languages are English and German 2026-02-02 13:49:35 +01:00
maziggy 89229a5ecc Add group-based permissions system with granular access control
Implement a full permissions system replacing simple admin/user roles:

Backend:
- Add Group model with many-to-many user relationship
- Add 50+ granular permissions (resource:action pattern)
- Create default groups: Administrators, Operators, Viewers
- Add permission-checking dependencies for route protection
- Add groups API endpoints (CRUD, user assignment)
- Add change password endpoint for users
- Update backup/restore to include groups
- Migrate existing users to groups on startup

Frontend:
- Add GroupsPage for managing groups and permissions
- Add permission helpers to AuthContext (hasPermission, hasAnyPermission)
- Add PermissionRoute component for protected routes
- Disable buttons/features based on permissions (with tooltips)
- Add change password modal in sidebar for all users
- Add forgot password info modal on login page
- Show user groups in UsersPage with group assignment

Testing:
- Add integration tests for groups API
- Add tests for user-group assignments
- Add tests for change password endpoint
- Seed default groups in test fixtures

Closes #28 #161
2026-01-31 12:50:15 +01:00
maziggy 888891fdc6 Add build plate empty detection feature
Automatically detect if objects are on the build plate before printing
and pause the print immediately if detected.

Features:
- Per-printer toggle to enable/disable plate detection
- Multi-reference calibration: store up to 5 reference images per printer
  for different plate types (textured, smooth, high-temp, etc.)
- Automatic print pause when objects detected at print start
- Push notification and WebSocket alert when print is paused
- ROI (Region of Interest) calibration UI with sliders to adjust
  detection area
- Reference management: view thumbnails, add labels, delete references
- Works with both built-in and external cameras
- Uses buffered camera frames when stream is active (no blocking)
- Split button UI: main button opens modal, chevron toggles on/off
- Green visual indicator when plate detection is enabled
- Included in backup/restore
2026-01-26 13:04:29 +01:00
maziggy ec83593456 Merge remote-tracking branch 'origin/main' into 0.1.6b11
# Conflicts:
#	backend/app/api/routes/auth.py
#	frontend/src/contexts/AuthContext.tsx
#	frontend/src/pages/SetupPage.tsx
#	frontend/src/pages/UsersPage.tsx
2026-01-23 11:19:49 +01:00
MartinNYHC a9f340f2c9 Revert "Added optional authentication and user management" 2026-01-21 15:58:24 +01:00
JesseFPV f8857ba666 Added optional authentication and user management 2026-01-21 14:10:06 +01:00
maziggy d48abe0f6a Add File Manager and enhanced Queue features
File Manager:
- Add full File Manager page for browsing local library files
- Implement folder hierarchy with create/rename/delete operations
- Add file uploads with drag-and-drop support
- Enable adding sliced files directly to print queue
- Add linked folders with external path mounting

Queue Enhancements:
- Add plate selection for multi-plate 3MF files
- Add print options (bed levelling, flow calibration, vibration
  calibration, layer inspection, timelapse, use AMS)
- Fix print_count increment to only occur on actual prints

Testing:
- Add backend integration tests for library API (15 tests)
- Add backend integration tests for queue print options (8 tests)
- Add frontend tests for EditQueueItemModal (10 tests)
- Suppress console noise in frontend test suite

Closes #94
2026-01-19 13:29:37 +01:00
maziggy b2831d09a2 Frontend ESLint Warnings (8 fixed)
┌─────────────────────────┬───────────────────────────────────────────────────────────┐
  │          File           │                            Fix                            │
  ├─────────────────────────┼───────────────────────────────────────────────────────────┤
  │ CameraPage.tsx:75       │ Copy imgRef.current to a variable before cleanup          │
  ├─────────────────────────┼───────────────────────────────────────────────────────────┤
  │ useWebSocket.ts:121     │ Add processMessageQueue to useCallback dependencies       │
  ├─────────────────────────┼───────────────────────────────────────────────────────────┤
  │ SpoolmanSettings.tsx:89 │ Add eslint-disable comment (intentional debounce pattern) │
  ├─────────────────────────┼───────────────────────────────────────────────────────────┤
  │ MQTTDebugModal.tsx:95   │ Wrap logs in useMemo to prevent recreating on each render │
  ├─────────────────────────┼───────────────────────────────────────────────────────────┤
  │ Layout.tsx:166          │ Wrap navItemsMap and extLinksMap in useMemo               │
  ├─────────────────────────┼───────────────────────────────────────────────────────────┤
  │ KProfilesView.tsx:715   │ Wrap getProfileKey in useCallback                         │
  ├─────────────────────────┼───────────────────────────────────────────────────────────┤
  │ GcodeViewer.tsx:171     │ Add eslint-disable comment (intentional behavior)         │
  └─────────────────────────┴───────────────────────────────────────────────────────────┘
  Docker Integration Test Fix

  The /api/v1/settings endpoint was returning 404 because:
  1. The route was defined at /settings/ (with trailing slash)
  2. Curl without -L doesn't follow redirects
  3. The catch-all route was intercepting API paths

  Fixes:
  - Added routes for both with and without trailing slash in settings.py and notification_templates.py
  - Updated catch-all in main.py to raise proper HTTPException for API routes

  Test Results

  - Frontend lint: 0 errors, 0 warnings
  - TypeScript: No errors
  - Backend unit tests: 264 passed
  - Backend integration tests: 309 passed
2026-01-19 13:29:37 +01:00
maziggy 6fd11ddc77 Add virtual printer Queue mode and sidebar badge indicators
Virtual Printer Changes:
- Add new "Queue" mode that archives files and adds them to print queue
- Rename modes: "Immediate" → "Archive", "Queue for Review" → "Review"
- Three modes now: Archive (immediate), Review (pending), Queue (print queue)
- Queue mode creates unassigned queue items (printer_id=null)

Print Queue Changes:
- Make printer_id nullable in PrintQueueItem model/schema
- Add support for unassigned queue items (no printer assigned)
- Queue page shows "Unassigned" filter option
- Edit modal allows assigning printer to unassigned items
- Unassigned items highlighted in orange

Sidebar Badge Indicators:
- Queue icon shows yellow badge with pending queue item count
- Archive icon shows blue badge with pending upload count
- Badges auto-update every 5 seconds and on window focus

Other:
- Update backup/restore to handle nullable printer_id and ams_mapping
- Add database migration for nullable printer_id column
- Update VirtualPrinterSettings tests for new modes
- Update virtual printer API integration tests
- Remove speed controls documentation from wiki (not implemented)
2026-01-17 13:30:00 +01:00
maziggy 50fd0c018b Add support bundle feature for issue reporting
- Add /api/v1/support/debug-logging endpoints to toggle debug log level
  - Add /api/v1/support/bundle endpoint to generate ZIP with system info and logs
  - Debug logging state persists across restarts via Settings database
  - Add debug logging indicator banner in Layout with real-time duration timer
  - Add Support & Troubleshooting section to System Information page
  - Privacy protection:
    - Filter sensitive settings (emails, keys, tokens, URLs, configs)
    - Sanitize paths to remove usernames
    - Remove hostname from collected data
    - Replace IP addresses with [IP] and emails with [EMAIL] in logs
  - Add privacy info panel explaining what data is/isn't collected
  - Require debug logging to be enabled before downloading support bundle
2026-01-05 09:54:31 +01:00
maziggy 61c8682fa9 Fixed dark theme's logo 2026-01-02 14:22:31 +01:00
maziggy bca1a80172 * Add customizable theme system with style, background, and accent options
Implement comprehensive theme customization with independent settings for
  dark and light modes:

  - Style layer: Classic (clean shadows), Glow (accent-colored glow effects),
    Vibrant (dramatic deep shadows)
  - Background layer: Neutral, Warm, Cool (light mode); plus OLED, Slate,
    Forest (dark mode only)
  - Accent colors: Green, Teal, Blue, Orange, Purple, Red

  All combinations work independently (e.g., Glow + Forest + Teal). Settings
  sync across devices via database and show toast confirmations on change.

  Backend:
  - Add 6 new settings fields (dark_style, dark_background, dark_accent,
    light_style, light_background, light_accent)
  - Add integration test for theme settings API

  Frontend:
  - Refactor index.css with 3-layer CSS variable system
  - Update ThemeContext for dual-mode theme management
  - Add Appearance section to Settings page with 6 dropdowns
  - Update components for new ThemeContext API
2025-12-31 15:44:52 +01:00
maziggy 88487f8f46 Bottom sidebar layout fix 2025-12-30 09:28:32 +01:00
maziggy 0bcedd2c16 - Add Tasmota device discovery and switchbar quick access
- Features:
    - Tasmota device discovery: Auto-detect network and scan for Tasmota devices
      in Add Smart Plug modal. Supports devices with/without authentication.
    - Switchbar: Quick access widget in sidebar footer for controlling smart plugs
      from anywhere in the app. Shows real-time status and power consumption.
2025-12-28 15:02:42 +01:00
maziggy 77a66ea867 Added system info page 2025-12-11 12:45:38 +01:00
maziggy d1518083fc ## New Features
### Projects / Print Grouping
  - Create projects to group related prints (e.g., "Voron Build" with 50 parts)
  - Track progress with target count and completion percentage
  - Assign archives to projects via edit modal or context menu
  - Project cards show archive thumbnails with clickable links
  - Color-coded project badges on archive cards
  - Filter and manage projects by status (active/completed/archived)

  ### Full-Text Search (FTS5)
  - SQLite FTS5 virtual table for efficient searching
  - Search across print_name, filename, tags, notes, designer, filament_type
  - Automatic index sync with triggers for INSERT/UPDATE/DELETE

  ### Webhooks & API Keys
  - API key authentication with granular permissions
  - Permissions: can_read_status, can_manage_queue, can_control_printer
  - Secure key generation with prefix display only after creation
  - Settings page API Keys tab for key management
  - Webhook endpoints for external integrations

  ### Failure Analysis
  - Dashboard widget showing failure rate with color coding
  - Correlate failures with conditions (filament type, printer, time)
  - Top failure reasons breakdown
  - Weekly trend visualization

  ### Archive Comparison
  - Select 2-5 archives to compare side-by-side
  - Highlight differences in print settings (yellow)
  - Success/failure correlation insights
  - Modal with close via button, X, Escape, or backdrop

  ### CSV/Excel Export
  - Export archives and statistics with current filters
  - Support for both CSV and Excel (.xlsx) formats
  - openpyxl dependency added

  ## Bug Fixes
  - Fixed context menu submenu not showing (removed overflow-hidden)
  - Fixed project card thumbnails using correct API endpoint
  - Fixed EditArchiveModal to invalidate projects query on save
  - Fixed clipboard API fallback for HTTP contexts
  - Fixed archive PATCH 500 error (FTS5 index rebuild)
  - Fixed FastAPI trailing slash routing for projects endpoint

  ## UI Improvements
  - Context menu submenu with hover/click support
  - Project badge on archive cards with project color
  - "Go to Project" context menu item for assigned archives
  - Clickable project card thumbnails linking to archives
  - Reset Layout button moved to Stats page header
2025-12-10 17:06:43 +00:00
maziggy f317ead3c6 Fixed bug where external links were not respected by hotkeys 2025-12-10 09:44:47 +00:00
maziggy 9504f4fc59 Fixed layout for external link icons 2025-12-09 13:20:09 +00:00