From d2ea32ef6f558b38c10fbce6cad4c82c1f564e1b Mon Sep 17 00:00:00 2001 From: Chris Bennight Date: Tue, 6 Oct 2026 06:19:38 -0400 Subject: [PATCH] Add Bambuddy Internal URL for Obico snapshots (#3255) --- backend/app/api/routes/obico.py | 2 +- backend/app/schemas/settings.py | 33 + backend/app/services/obico_detection.py | 19 +- backend/tests/integration/test_obico_api.py | 108 +- backend/tests/unit/test_obico_detection.py | 30 +- .../unit/test_outbound_url_ssrf_guards.py | 4 +- .../FailureDetectionSettings.test.tsx | 49 + frontend/src/api/client.ts | 1 + .../components/FailureDetectionSettings.tsx | 46 +- frontend/src/i18n/locales/de.ts | 7 +- frontend/src/i18n/locales/en.ts | 7 +- frontend/src/i18n/locales/es.ts | 7 +- frontend/src/i18n/locales/fr.ts | 7 +- frontend/src/i18n/locales/it.ts | 7 +- frontend/src/i18n/locales/ja.ts | 7 +- frontend/src/i18n/locales/ko.ts | 7 +- frontend/src/i18n/locales/nl.ts | 7 +- frontend/src/i18n/locales/pt-BR.ts | 7 +- frontend/src/i18n/locales/ru.ts | 7 +- frontend/src/i18n/locales/sv.ts | 7 +- frontend/src/i18n/locales/tr.ts | 7 +- frontend/src/i18n/locales/uk.ts | 7 +- frontend/src/i18n/locales/zh-CN.ts | 7 +- frontend/src/i18n/locales/zh-TW.ts | 7 +- static/assets/AdditionalSection-Bcoa_hnW.js | 1 - static/assets/AmsUnitCard-DLM3L08d.js | 1 - static/assets/ArchivesPage-MaOGuDlA.js | 9 - static/assets/AreaChart-J0I0YlYN.js | 1 - static/assets/AssignToAmsModal-kr-0EzwV.js | 1 - static/assets/Button-pwVKPiYn.js | 1 - static/assets/CamWallPage-C1rIEzhG.js | 1 - static/assets/CameraDiagnoseModal-DWbtempu.js | 1 - static/assets/CameraPage-C1mWeQfG.js | 1 - static/assets/CameraWall-Br9MDE6G.js | 1 - static/assets/Card-OUAj0Ayh.js | 1 - static/assets/CartesianChart-DcboTNtX.js | 33 - static/assets/ColumnConfigModal-B9cmtxVm.js | 1 - .../assets/ConfigureAmsSlotModal-C4N9v6PJ.js | 1 - .../assets/ConnectAuthorizePage-DtVlpfFV.js | 1 - static/assets/ContextMenu-CqE3-J0R.js | 1 - static/assets/CopyButton-CyNT06yS.js | 1 - static/assets/ErrorBarContext-6wvnfk_q.js | 1 - static/assets/ExternalLinkPage-CM4__1rh.js | 1 - static/assets/FileManagerPage-Kp8jeXP4.js | 15 - static/assets/FileUploadModal-hFWmsB_i.js | 2 - static/assets/FinancePage-BfhaugR1.js | 1 - static/assets/GCodeViewerPage-C24Va31U.js | 1 - static/assets/GcodeToolpathViewer-D4M17KI8.js | 129 - static/assets/GroupEditPage-i9TLWDyl.js | 1 - static/assets/ImagePreviewModal-BcWeiOrd.js | 1 - static/assets/InventoryPage-BWTPGneX.js | 2 - static/assets/LibraryTrashPage-Ck7R9blU.js | 1 - static/assets/LoginPage-CXIN3BLj.js | 1 - static/assets/MaintenancePage-BAMWk5DI.js | 1 - static/assets/ModelSourcesPage-C9OvAi9p.js | 1 - static/assets/NotificationsPage-5GOJDRkw.js | 1 - static/assets/OrbitControls-Bbqf07B-.js | 3947 ----------------- static/assets/OverlayFrame-BUFy7NI1.js | 1 - static/assets/PdfPreviewModal-CRMSWXkI.js | 2 - static/assets/PhotoGalleryModal-BxTCJXU3.js | 1 - .../assets/PrinterLocationsPage-D2AqeJ9i.js | 1 - static/assets/PrintersPage-zDUFDj5q.js | 49 - static/assets/ProfilesPage-C0jHVdln.js | 13 - static/assets/ProjectDetailPage-CaZEFrXK.js | 1 - static/assets/ProjectsPage-C2aXY-5x.js | 1 - static/assets/QueryClientProvider-Clbyr4Jg.js | 1 - static/assets/QueuePage-DTh2aCRG.js | 16 - static/assets/ReferenceLine-l3CI8D5f.js | 4 - static/assets/RichTextEditor-CtyYZFiu.js | 129 - static/assets/SettingsPage-DOroBYzD.js | 7 - static/assets/SetupPage-hUHebIlA.js | 1 - static/assets/SliceModal-DilRo4MU.js | 2 - static/assets/SpoolBuddyAmsPage-CCeWMoIm.js | 1 - .../SpoolBuddyCalibrationPage-BPEaykbd.js | 1 - static/assets/SpoolBuddyDashboard-B_0MmvgI.js | 1 - .../SpoolBuddyInventoryPage-BmxJPbmk.js | 1 - .../assets/SpoolBuddySettingsPage-DKia6Rid.js | 2 - .../assets/SpoolBuddyWriteTagPage-CMlhWgSL.js | 1 - static/assets/SpoolIcon-OZKzbEc8.js | 1 - .../SpreadsheetPreviewModal-WUjrw07T.js | 2 - static/assets/StatsPage-D0cqrND3.js | 1 - static/assets/StreamOverlayPage-CA9KpVK8.css | 1 - static/assets/StreamOverlayPage-DWOhiuT-.js | 1 - static/assets/SystemInfoPage-ClAReSss.js | 3 - static/assets/ToastContext-VFOrCCoA.js | 1 - static/assets/Toggle-CwMiiK0g.js | 1 - static/assets/arrow-down-C4OBNDqJ.js | 1 - static/assets/arrow-left-MSgMxOFV.js | 1 - static/assets/arrow-right-Da96kh4S.js | 1 - static/assets/arrow-up-Dam8cLfE.js | 1 - static/assets/arrow-up-down-C-1Kkt1x.js | 1 - static/assets/box-BIRPxdgi.js | 1 - static/assets/briefcase-BmuFBTLZ.js | 1 - static/assets/calendar-DiVEdzUw.js | 1 - static/assets/check-D1Pn8T1J.js | 1 - static/assets/chevrons-right-OnQ-9jKz.js | 1 - static/assets/chunk-aKtaBQYM.js | 1 - static/assets/client-BdOyoZJz.js | 1 - static/assets/cloud-off-DmnG-KIw.js | 1 - static/assets/cog-pa-nvQzt.js | 1 - static/assets/coins-6tqy9spE.js | 1 - static/assets/columns-2-CGEUxYp_.js | 1 - static/assets/copy-3N5VKzXz.js | 1 - static/assets/cpu-DAX4KhDZ.js | 1 - static/assets/createLucideIcon-Yo2YoYjm.js | 1 - static/assets/date-G9KUauqz.js | 1 - static/assets/de-DvnDPDo7.js | 4 - static/assets/dollar-sign-ChtX2KHo.js | 1 - static/assets/droplet-PpnaB65N.js | 1 - static/assets/ellipsis-vertical-So0E77JK.js | 1 - static/assets/es-DZZ0IPDC.js | 4 - static/assets/eye-off-B6dY82pM.js | 1 - static/assets/fan-DSlOeWAh.js | 1 - .../assets/filamentSwatchHelpers-D3oXQSGk.js | 1 - static/assets/file-DDkFXH-b.js | 1 - static/assets/file-box-BjB1o93N.js | 1 - static/assets/file-spreadsheet-8-W2zdLi.js | 1 - static/assets/fr-D9McbtW3.js | 4 - static/assets/funnel-BtI48m95.js | 1 - static/assets/gauge-xO5PdXj0.js | 1 - static/assets/git-compare-DbgnTP6k.js | 1 - static/assets/grip-vertical-D4urhrwn.js | 1 - static/assets/haSensorDisplay-CSo8Nw_c.js | 1 - static/assets/hard-drive-CN9rq6Uq.js | 1 - static/assets/history-BBZbf3sJ.js | 1 - static/assets/index-BoVbGTav.js | 24 - static/assets/index-jO4mF6Tu.css | 2 - static/assets/info-DcT0QU8O.js | 1 - static/assets/inventorySearch-yJim1_Yj.js | 1 - static/assets/it-BpOecomy.js | 4 - static/assets/ja-T2xC1zs8.js | 4 - static/assets/ko-C1--UcOC.js | 4 - static/assets/layout-grid-D4S7CgZJ.js | 1 - static/assets/libraryFiles-B0B1SFfn.js | 1 - static/assets/link-2-Bj1oMj3O.js | 1 - static/assets/list-CBXEiN6g.js | 1 - static/assets/loader-circle-CB4leTZa.js | 1 - .../assets/locationSensorDefaults-rSWWVL7r.js | 1 - static/assets/lock-C288ZvMI.js | 1 - static/assets/log-in-D7NdqHOf.js | 1 - static/assets/maximize-2-Cggm3z8J.js | 1 - static/assets/minimize-2-BLq3FJZO.js | 1 - static/assets/minus-D0aY9wbr.js | 1 - static/assets/move-CEngqF8v.js | 1 - static/assets/nl-CtZfK3Nf.js | 4 - static/assets/occt-import-js-BhHfLpto.wasm | Bin 7604031 -> 0 bytes static/assets/package-D9xR0eOt.js | 1 - static/assets/papaparse.min-CR43Mv99.js | 10 - static/assets/pause-CPKrVlsD.js | 1 - static/assets/pdf-DtHaoVwF.js | 56 - static/assets/pdf.worker.min-0idpi3xP.js | 50 - static/assets/pdf.worker.min-DpOxPwGP.js | 1 - static/assets/pdfjs/cmaps/78-EUC-H.bcmap | Bin 2404 -> 0 bytes static/assets/pdfjs/cmaps/78-EUC-V.bcmap | Bin 173 -> 0 bytes static/assets/pdfjs/cmaps/78-H.bcmap | Bin 2379 -> 0 bytes static/assets/pdfjs/cmaps/78-RKSJ-H.bcmap | Bin 2398 -> 0 bytes static/assets/pdfjs/cmaps/78-RKSJ-V.bcmap | Bin 173 -> 0 bytes static/assets/pdfjs/cmaps/78-V.bcmap | Bin 169 -> 0 bytes static/assets/pdfjs/cmaps/78ms-RKSJ-H.bcmap | Bin 2651 -> 0 bytes static/assets/pdfjs/cmaps/78ms-RKSJ-V.bcmap | Bin 290 -> 0 bytes static/assets/pdfjs/cmaps/83pv-RKSJ-H.bcmap | Bin 905 -> 0 bytes static/assets/pdfjs/cmaps/90ms-RKSJ-H.bcmap | Bin 721 -> 0 bytes static/assets/pdfjs/cmaps/90ms-RKSJ-V.bcmap | Bin 290 -> 0 bytes static/assets/pdfjs/cmaps/90msp-RKSJ-H.bcmap | Bin 715 -> 0 bytes static/assets/pdfjs/cmaps/90msp-RKSJ-V.bcmap | Bin 291 -> 0 bytes static/assets/pdfjs/cmaps/90pv-RKSJ-H.bcmap | Bin 982 -> 0 bytes static/assets/pdfjs/cmaps/90pv-RKSJ-V.bcmap | Bin 260 -> 0 bytes static/assets/pdfjs/cmaps/Add-H.bcmap | Bin 2419 -> 0 bytes static/assets/pdfjs/cmaps/Add-RKSJ-H.bcmap | Bin 2413 -> 0 bytes static/assets/pdfjs/cmaps/Add-RKSJ-V.bcmap | Bin 287 -> 0 bytes static/assets/pdfjs/cmaps/Add-V.bcmap | Bin 282 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-CNS1-0.bcmap | Bin 317 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-CNS1-1.bcmap | Bin 371 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-CNS1-2.bcmap | Bin 376 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-CNS1-3.bcmap | Bin 401 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-CNS1-4.bcmap | Bin 405 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-CNS1-5.bcmap | Bin 406 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-CNS1-6.bcmap | Bin 406 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap | Bin 41193 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-GB1-0.bcmap | Bin 217 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-GB1-1.bcmap | Bin 250 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-GB1-2.bcmap | Bin 465 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-GB1-3.bcmap | Bin 470 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-GB1-4.bcmap | Bin 601 -> 0 bytes static/assets/pdfjs/cmaps/Adobe-GB1-5.bcmap | Bin 625 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap | Bin 33974 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Japan1-0.bcmap | Bin 225 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Japan1-1.bcmap | Bin 226 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Japan1-2.bcmap | Bin 233 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Japan1-3.bcmap | Bin 242 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Japan1-4.bcmap | Bin 337 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Japan1-5.bcmap | Bin 430 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Japan1-6.bcmap | Bin 485 -> 0 bytes .../pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap | Bin 40951 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Korea1-0.bcmap | Bin 241 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Korea1-1.bcmap | Bin 386 -> 0 bytes .../assets/pdfjs/cmaps/Adobe-Korea1-2.bcmap | Bin 391 -> 0 bytes .../pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap | Bin 23293 -> 0 bytes static/assets/pdfjs/cmaps/B5-H.bcmap | Bin 1086 -> 0 bytes static/assets/pdfjs/cmaps/B5-V.bcmap | Bin 142 -> 0 bytes static/assets/pdfjs/cmaps/B5pc-H.bcmap | Bin 1099 -> 0 bytes static/assets/pdfjs/cmaps/B5pc-V.bcmap | Bin 144 -> 0 bytes static/assets/pdfjs/cmaps/CNS-EUC-H.bcmap | Bin 1780 -> 0 bytes static/assets/pdfjs/cmaps/CNS-EUC-V.bcmap | Bin 1920 -> 0 bytes static/assets/pdfjs/cmaps/CNS1-H.bcmap | Bin 706 -> 0 bytes static/assets/pdfjs/cmaps/CNS1-V.bcmap | Bin 143 -> 0 bytes static/assets/pdfjs/cmaps/CNS2-H.bcmap | Bin 504 -> 0 bytes static/assets/pdfjs/cmaps/CNS2-V.bcmap | 3 - static/assets/pdfjs/cmaps/ETHK-B5-H.bcmap | Bin 4426 -> 0 bytes static/assets/pdfjs/cmaps/ETHK-B5-V.bcmap | Bin 158 -> 0 bytes static/assets/pdfjs/cmaps/ETen-B5-H.bcmap | Bin 1125 -> 0 bytes static/assets/pdfjs/cmaps/ETen-B5-V.bcmap | Bin 158 -> 0 bytes static/assets/pdfjs/cmaps/ETenms-B5-H.bcmap | 3 - static/assets/pdfjs/cmaps/ETenms-B5-V.bcmap | Bin 172 -> 0 bytes static/assets/pdfjs/cmaps/EUC-H.bcmap | Bin 578 -> 0 bytes static/assets/pdfjs/cmaps/EUC-V.bcmap | Bin 170 -> 0 bytes static/assets/pdfjs/cmaps/Ext-H.bcmap | Bin 2536 -> 0 bytes static/assets/pdfjs/cmaps/Ext-RKSJ-H.bcmap | Bin 2542 -> 0 bytes static/assets/pdfjs/cmaps/Ext-RKSJ-V.bcmap | Bin 218 -> 0 bytes static/assets/pdfjs/cmaps/Ext-V.bcmap | Bin 215 -> 0 bytes static/assets/pdfjs/cmaps/GB-EUC-H.bcmap | Bin 549 -> 0 bytes static/assets/pdfjs/cmaps/GB-EUC-V.bcmap | Bin 179 -> 0 bytes static/assets/pdfjs/cmaps/GB-H.bcmap | 4 - static/assets/pdfjs/cmaps/GB-V.bcmap | Bin 175 -> 0 bytes static/assets/pdfjs/cmaps/GBK-EUC-H.bcmap | Bin 14692 -> 0 bytes static/assets/pdfjs/cmaps/GBK-EUC-V.bcmap | Bin 180 -> 0 bytes static/assets/pdfjs/cmaps/GBK2K-H.bcmap | Bin 19662 -> 0 bytes static/assets/pdfjs/cmaps/GBK2K-V.bcmap | Bin 219 -> 0 bytes static/assets/pdfjs/cmaps/GBKp-EUC-H.bcmap | Bin 14686 -> 0 bytes static/assets/pdfjs/cmaps/GBKp-EUC-V.bcmap | Bin 181 -> 0 bytes static/assets/pdfjs/cmaps/GBT-EUC-H.bcmap | Bin 7290 -> 0 bytes static/assets/pdfjs/cmaps/GBT-EUC-V.bcmap | Bin 180 -> 0 bytes static/assets/pdfjs/cmaps/GBT-H.bcmap | Bin 7269 -> 0 bytes static/assets/pdfjs/cmaps/GBT-V.bcmap | Bin 176 -> 0 bytes static/assets/pdfjs/cmaps/GBTpc-EUC-H.bcmap | Bin 7298 -> 0 bytes static/assets/pdfjs/cmaps/GBTpc-EUC-V.bcmap | Bin 182 -> 0 bytes static/assets/pdfjs/cmaps/GBpc-EUC-H.bcmap | Bin 557 -> 0 bytes static/assets/pdfjs/cmaps/GBpc-EUC-V.bcmap | Bin 181 -> 0 bytes static/assets/pdfjs/cmaps/H.bcmap | Bin 553 -> 0 bytes static/assets/pdfjs/cmaps/HKdla-B5-H.bcmap | Bin 2654 -> 0 bytes static/assets/pdfjs/cmaps/HKdla-B5-V.bcmap | Bin 148 -> 0 bytes static/assets/pdfjs/cmaps/HKdlb-B5-H.bcmap | Bin 2414 -> 0 bytes static/assets/pdfjs/cmaps/HKdlb-B5-V.bcmap | Bin 148 -> 0 bytes static/assets/pdfjs/cmaps/HKgccs-B5-H.bcmap | Bin 2292 -> 0 bytes static/assets/pdfjs/cmaps/HKgccs-B5-V.bcmap | Bin 149 -> 0 bytes static/assets/pdfjs/cmaps/HKm314-B5-H.bcmap | Bin 1772 -> 0 bytes static/assets/pdfjs/cmaps/HKm314-B5-V.bcmap | Bin 149 -> 0 bytes static/assets/pdfjs/cmaps/HKm471-B5-H.bcmap | Bin 2171 -> 0 bytes static/assets/pdfjs/cmaps/HKm471-B5-V.bcmap | Bin 149 -> 0 bytes static/assets/pdfjs/cmaps/HKscs-B5-H.bcmap | Bin 4437 -> 0 bytes static/assets/pdfjs/cmaps/HKscs-B5-V.bcmap | Bin 159 -> 0 bytes static/assets/pdfjs/cmaps/Hankaku.bcmap | Bin 132 -> 0 bytes static/assets/pdfjs/cmaps/Hiragana.bcmap | Bin 124 -> 0 bytes static/assets/pdfjs/cmaps/KSC-EUC-H.bcmap | Bin 1848 -> 0 bytes static/assets/pdfjs/cmaps/KSC-EUC-V.bcmap | Bin 164 -> 0 bytes static/assets/pdfjs/cmaps/KSC-H.bcmap | Bin 1831 -> 0 bytes static/assets/pdfjs/cmaps/KSC-Johab-H.bcmap | Bin 16791 -> 0 bytes static/assets/pdfjs/cmaps/KSC-Johab-V.bcmap | Bin 166 -> 0 bytes static/assets/pdfjs/cmaps/KSC-V.bcmap | Bin 160 -> 0 bytes static/assets/pdfjs/cmaps/KSCms-UHC-H.bcmap | Bin 2787 -> 0 bytes .../assets/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap | Bin 2789 -> 0 bytes .../assets/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap | Bin 169 -> 0 bytes static/assets/pdfjs/cmaps/KSCms-UHC-V.bcmap | Bin 166 -> 0 bytes static/assets/pdfjs/cmaps/KSCpc-EUC-H.bcmap | Bin 2024 -> 0 bytes static/assets/pdfjs/cmaps/KSCpc-EUC-V.bcmap | Bin 166 -> 0 bytes static/assets/pdfjs/cmaps/Katakana.bcmap | Bin 100 -> 0 bytes static/assets/pdfjs/cmaps/LICENSE | 36 - static/assets/pdfjs/cmaps/NWP-H.bcmap | Bin 2765 -> 0 bytes static/assets/pdfjs/cmaps/NWP-V.bcmap | Bin 252 -> 0 bytes static/assets/pdfjs/cmaps/RKSJ-H.bcmap | Bin 534 -> 0 bytes static/assets/pdfjs/cmaps/RKSJ-V.bcmap | Bin 170 -> 0 bytes static/assets/pdfjs/cmaps/Roman.bcmap | Bin 96 -> 0 bytes static/assets/pdfjs/cmaps/UniCNS-UCS2-H.bcmap | Bin 48280 -> 0 bytes static/assets/pdfjs/cmaps/UniCNS-UCS2-V.bcmap | Bin 156 -> 0 bytes .../assets/pdfjs/cmaps/UniCNS-UTF16-H.bcmap | Bin 50419 -> 0 bytes .../assets/pdfjs/cmaps/UniCNS-UTF16-V.bcmap | Bin 156 -> 0 bytes .../assets/pdfjs/cmaps/UniCNS-UTF32-H.bcmap | Bin 52679 -> 0 bytes .../assets/pdfjs/cmaps/UniCNS-UTF32-V.bcmap | Bin 160 -> 0 bytes static/assets/pdfjs/cmaps/UniCNS-UTF8-H.bcmap | Bin 53629 -> 0 bytes static/assets/pdfjs/cmaps/UniCNS-UTF8-V.bcmap | Bin 157 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UCS2-H.bcmap | Bin 43366 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UCS2-V.bcmap | Bin 193 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UTF16-H.bcmap | Bin 44086 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UTF16-V.bcmap | Bin 178 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UTF32-H.bcmap | Bin 45738 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UTF32-V.bcmap | Bin 182 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UTF8-H.bcmap | Bin 46837 -> 0 bytes static/assets/pdfjs/cmaps/UniGB-UTF8-V.bcmap | Bin 181 -> 0 bytes static/assets/pdfjs/cmaps/UniJIS-UCS2-H.bcmap | Bin 25439 -> 0 bytes .../assets/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap | Bin 119 -> 0 bytes .../assets/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap | Bin 680 -> 0 bytes static/assets/pdfjs/cmaps/UniJIS-UCS2-V.bcmap | Bin 664 -> 0 bytes .../assets/pdfjs/cmaps/UniJIS-UTF16-H.bcmap | Bin 39443 -> 0 bytes .../assets/pdfjs/cmaps/UniJIS-UTF16-V.bcmap | Bin 643 -> 0 bytes .../assets/pdfjs/cmaps/UniJIS-UTF32-H.bcmap | Bin 40539 -> 0 bytes .../assets/pdfjs/cmaps/UniJIS-UTF32-V.bcmap | Bin 677 -> 0 bytes static/assets/pdfjs/cmaps/UniJIS-UTF8-H.bcmap | Bin 41695 -> 0 bytes static/assets/pdfjs/cmaps/UniJIS-UTF8-V.bcmap | Bin 678 -> 0 bytes .../pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap | Bin 39534 -> 0 bytes .../pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap | Bin 647 -> 0 bytes .../pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap | Bin 40630 -> 0 bytes .../pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap | Bin 681 -> 0 bytes .../pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap | Bin 41779 -> 0 bytes .../pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap | Bin 682 -> 0 bytes .../pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap | Bin 705 -> 0 bytes .../assets/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap | Bin 689 -> 0 bytes .../assets/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap | Bin 726 -> 0 bytes .../pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap | Bin 40517 -> 0 bytes .../pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap | Bin 684 -> 0 bytes .../pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap | Bin 40608 -> 0 bytes .../pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap | Bin 688 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UCS2-H.bcmap | Bin 25783 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UCS2-V.bcmap | Bin 178 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UTF16-H.bcmap | Bin 26327 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UTF16-V.bcmap | Bin 164 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UTF32-H.bcmap | Bin 26451 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UTF32-V.bcmap | Bin 168 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UTF8-H.bcmap | Bin 27790 -> 0 bytes static/assets/pdfjs/cmaps/UniKS-UTF8-V.bcmap | Bin 169 -> 0 bytes static/assets/pdfjs/cmaps/V.bcmap | Bin 166 -> 0 bytes static/assets/pdfjs/cmaps/WP-Symbol.bcmap | Bin 179 -> 0 bytes .../pdfjs/iccs/CGATS001Compat-v2-micro.icc | Bin 8464 -> 0 bytes static/assets/pdfjs/iccs/LICENSE | 116 - .../pdfjs/standard_fonts/FoxitDingbats.pfb | Bin 29513 -> 0 bytes .../pdfjs/standard_fonts/FoxitFixed.pfb | Bin 17597 -> 0 bytes .../pdfjs/standard_fonts/FoxitFixedBold.pfb | Bin 18055 -> 0 bytes .../standard_fonts/FoxitFixedBoldItalic.pfb | Bin 19151 -> 0 bytes .../pdfjs/standard_fonts/FoxitFixedItalic.pfb | Bin 18746 -> 0 bytes .../pdfjs/standard_fonts/FoxitSerif.pfb | Bin 19469 -> 0 bytes .../pdfjs/standard_fonts/FoxitSerifBold.pfb | Bin 19395 -> 0 bytes .../standard_fonts/FoxitSerifBoldItalic.pfb | Bin 20733 -> 0 bytes .../pdfjs/standard_fonts/FoxitSerifItalic.pfb | Bin 21227 -> 0 bytes .../pdfjs/standard_fonts/FoxitSymbol.pfb | Bin 16729 -> 0 bytes .../assets/pdfjs/standard_fonts/LICENSE_FOXIT | 27 - .../pdfjs/standard_fonts/LICENSE_LIBERATION | 102 - .../standard_fonts/LiberationSans-Bold.ttf | Bin 137052 -> 0 bytes .../LiberationSans-BoldItalic.ttf | Bin 135124 -> 0 bytes .../standard_fonts/LiberationSans-Italic.ttf | Bin 162036 -> 0 bytes .../standard_fonts/LiberationSans-Regular.ttf | Bin 139512 -> 0 bytes static/assets/pdfjs/wasm/LICENSE_JBIG2 | 196 - static/assets/pdfjs/wasm/LICENSE_OPENJPEG | 39 - static/assets/pdfjs/wasm/LICENSE_PDFJS_JBIG2 | 13 - .../assets/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG | 22 - static/assets/pdfjs/wasm/LICENSE_PDFJS_QCMS | 22 - static/assets/pdfjs/wasm/LICENSE_QCMS | 21 - static/assets/pdfjs/wasm/jbig2.wasm | Bin 104852 -> 0 bytes .../pdfjs/wasm/jbig2_nowasm_fallback.js | 15 - static/assets/pdfjs/wasm/openjpeg.wasm | Bin 252032 -> 0 bytes .../pdfjs/wasm/openjpeg_nowasm_fallback.js | 17 - static/assets/pdfjs/wasm/qcms_bg.wasm | Bin 96589 -> 0 bytes static/assets/pdfjs/wasm/quickjs-eval.js | 16 - static/assets/pdfjs/wasm/quickjs-eval.wasm | Bin 469105 -> 0 bytes static/assets/pen-line-DYnjxVtS.js | 1 - static/assets/play-DROvjtg7.js | 1 - static/assets/printerModel-DrNvERGQ.js | 1 - static/assets/process-schema-CzfeynAH.js | 277 -- .../assets/process-toggle-rules-DDBax3G5.js | 1 - static/assets/process-ui-tree-BWrKRLV6.js | 1 - static/assets/pt-BR-CrNWoBDc.js | 4 - static/assets/purify.es-F_7NWVtX.js | 3 - static/assets/qr-code-PQYGpSrt.js | 1 - static/assets/queueItemName-UFY7kpjG.js | 1 - static/assets/react-dom-xhkz5--P.js | 1 - static/assets/ru-BH6X9QDA.js | 4 - static/assets/scale-lRomj4j_.js | 1 - static/assets/scan-search-LN9I03Ga.js | 1 - static/assets/search-BpAJdxRw.js | 1 - static/assets/settings-2-DYB5IzKV.js | 1 - static/assets/shield-CfkeOiiL.js | 1 - static/assets/skip-forward-BE5zCMec.js | 1 - static/assets/slicer-DgCA7cW6.js | 1 - static/assets/slicerPrinterMatch-g7Dr6p0c.js | 1 - static/assets/smartphone-DOKM2tP4.js | 1 - static/assets/sortable.esm-uF6WmP46.js | 5 - static/assets/sparkles-AUof3WTI.js | 1 - static/assets/spoolPaint-CrvpHqdD.js | 1 - static/assets/stepPreview.worker-QYcQPnWg.js | 14 - static/assets/sticky-note-C75KNLfY.js | 1 - static/assets/sv-BB_QxdtQ.js | 4 - static/assets/target-PpftuQXz.js | 1 - static/assets/timer-BYBW_ysJ.js | 1 - static/assets/tr-BBzKsz9z.js | 4 - static/assets/triangle-alert-gIik0G2C.js | 1 - static/assets/uk-BYvqnFMj.js | 4 - static/assets/unlink-DJBJ04VB.js | 1 - .../assets/useCancellableTimeout-CXuQGelB.js | 1 - static/assets/useMutation-qh3Iyscy.js | 1 - .../assets/usePreviewFullscreen-DOsgAeT7.js | 1 - static/assets/useQueries-C87lFrjE.js | 1 - static/assets/useQuery-CCy0EwsM.js | 1 - static/assets/utils-D0ctH_jZ.js | 1 - static/assets/wind-CsHQYbhI.js | 1 - static/assets/with-selector-C2KEMbwS.js | 1 - static/assets/workflow-DxBrfirU.js | 1 - static/assets/x-Dctff5l9.js | 1 - static/assets/xlsx-CKkngM-o.js | 141 - static/assets/zh-CN-zENHv8ar.js | 4 - static/assets/zh-TW-BbdM61yf.js | 4 - static/assets/zoom-out-C85W_TC5.js | 1 - static/fonts/inter-latin-ext.woff2 | Bin 85068 -> 0 bytes static/fonts/inter-latin.woff2 | Bin 48256 -> 0 bytes static/icons/ams-ht.png | Bin 4314 -> 0 bytes static/icons/ams-settings.svg | 3 - static/icons/ams-wiring-center.svg | 9 - static/icons/ams-wiring-left.svg | 17 - static/icons/ams-wiring-right.svg | 17 - static/icons/ams.png | Bin 6447 -> 0 bytes static/icons/chamber.svg | 1 - static/icons/dual-extruder-left.png | Bin 13160 -> 0 bytes static/icons/dual-extruder-right.png | Bin 13039 -> 0 bytes static/icons/dual-extruder-right_sav.png | Bin 19716 -> 0 bytes static/icons/dual-extruder.png | Bin 19576 -> 0 bytes static/icons/extruder-change-filament.png | Bin 169714 -> 0 bytes static/icons/extruder-left-right.png | Bin 5775 -> 0 bytes static/icons/eye.svg | 51 - static/icons/heatbed.svg | 1 - static/icons/home.svg | 44 - static/icons/hotend.svg | 6 - static/icons/humidity-empty.svg | 4 - static/icons/humidity-full.svg | 4 - static/icons/humidity-half.svg | 4 - static/icons/jogpad.png | Bin 63625 -> 0 bytes static/icons/jogpad.svg | 46 - static/icons/lamp.svg | 4 - static/icons/micro-sd.svg | 12 - static/icons/reload.svg | 1 - static/icons/settings.svg | 1 - static/icons/single-extruder1.png | Bin 5124 -> 0 bytes static/icons/single-extruder2.png | Bin 14109 -> 0 bytes static/icons/skip-objects.svg | 1 - static/icons/snowflake.svg | 53 - static/icons/speed.svg | 6 - static/icons/temperature.svg | 4 - static/icons/ventilation.svg | 6 - static/icons/video-camera.svg | 1 - static/icons/water.svg | 2 - static/icons/webcam.svg | 73 - static/img/android-chrome-192x192.png | Bin 43759 -> 0 bytes static/img/android-chrome-512x512.png | Bin 206875 -> 0 bytes static/img/apple-touch-icon.png | Bin 39147 -> 0 bytes static/img/bambuddy_logo_dark.png | Bin 608505 -> 0 bytes static/img/bambuddy_logo_dark_transparent.png | Bin 514022 -> 0 bytes static/img/bambuddy_logo_light.png | Bin 559482 -> 0 bytes static/img/bambuddy_logo_powered_by.png | Bin 67595 -> 0 bytes static/img/bed/bed_cool.png | Bin 3318 -> 0 bytes static/img/bed/bed_cool_supertack.png | Bin 2343 -> 0 bytes static/img/bed/bed_engineering.png | Bin 2828 -> 0 bytes static/img/bed/bed_high_templ.png | Bin 2440 -> 0 bytes static/img/bed/bed_pei.png | Bin 3399 -> 0 bytes static/img/bed/bed_pei_cool.png | Bin 5345 -> 0 bytes static/img/favicon-16x16.png | Bin 983 -> 0 bytes static/img/favicon-32x32.png | Bin 2534 -> 0 bytes static/img/favicon.png | Bin 233437 -> 0 bytes static/img/printers/a1.png | Bin 53321 -> 0 bytes static/img/printers/a1f.png | Bin 42422 -> 0 bytes static/img/printers/a1mini.png | Bin 46241 -> 0 bytes static/img/printers/a2l.png | Bin 39494 -> 0 bytes static/img/printers/default.png | Bin 107223 -> 0 bytes static/img/printers/h2c.png | Bin 153461 -> 0 bytes static/img/printers/h2d.png | Bin 88346 -> 0 bytes static/img/printers/h2dpro.png | Bin 43678 -> 0 bytes static/img/printers/o1c.png | Bin 52036 -> 0 bytes static/img/printers/o1e.png | Bin 46218 -> 0 bytes static/img/printers/o1s.png | Bin 61075 -> 0 bytes static/img/printers/p1p.png | Bin 93904 -> 0 bytes static/img/printers/p1s.png | Bin 56977 -> 0 bytes static/img/printers/printer_placeholder.png | Bin 2936 -> 0 bytes static/img/printers/x1c.png | Bin 92554 -> 0 bytes static/img/printers/x1e.png | Bin 50173 -> 0 bytes static/img/printers/x2d.png | Bin 54194 -> 0 bytes static/img/screenshot-desktop.png | Bin 128330 -> 0 bytes static/img/screenshot-mobile.png | Bin 129704 -> 0 bytes static/img/sponsors/northpole-3d-printing.jpg | Bin 133973 -> 0 bytes static/img/sponsors/notify.png | Bin 221781 -> 0 bytes static/img/spoolbuddy_logo_dark.png | Bin 114657 -> 0 bytes static/img/spoolbuddy_logo_dark_small.png | Bin 13365 -> 0 bytes static/index.html | 62 - static/manifest.json | 95 - static/spoolbuddy_logo_dark.png | Bin 114657 -> 0 bytes static/sw-register.js | 37 - static/sw.js | 250 -- static/vite.svg | 1 - 482 files changed, 339 insertions(+), 6662 deletions(-) delete mode 100644 static/assets/AdditionalSection-Bcoa_hnW.js delete mode 100644 static/assets/AmsUnitCard-DLM3L08d.js delete mode 100644 static/assets/ArchivesPage-MaOGuDlA.js delete mode 100644 static/assets/AreaChart-J0I0YlYN.js delete mode 100644 static/assets/AssignToAmsModal-kr-0EzwV.js delete mode 100644 static/assets/Button-pwVKPiYn.js delete mode 100644 static/assets/CamWallPage-C1rIEzhG.js delete mode 100644 static/assets/CameraDiagnoseModal-DWbtempu.js delete mode 100644 static/assets/CameraPage-C1mWeQfG.js delete mode 100644 static/assets/CameraWall-Br9MDE6G.js delete mode 100644 static/assets/Card-OUAj0Ayh.js delete mode 100644 static/assets/CartesianChart-DcboTNtX.js delete mode 100644 static/assets/ColumnConfigModal-B9cmtxVm.js delete mode 100644 static/assets/ConfigureAmsSlotModal-C4N9v6PJ.js delete mode 100644 static/assets/ConnectAuthorizePage-DtVlpfFV.js delete mode 100644 static/assets/ContextMenu-CqE3-J0R.js delete mode 100644 static/assets/CopyButton-CyNT06yS.js delete mode 100644 static/assets/ErrorBarContext-6wvnfk_q.js delete mode 100644 static/assets/ExternalLinkPage-CM4__1rh.js delete mode 100644 static/assets/FileManagerPage-Kp8jeXP4.js delete mode 100644 static/assets/FileUploadModal-hFWmsB_i.js delete mode 100644 static/assets/FinancePage-BfhaugR1.js delete mode 100644 static/assets/GCodeViewerPage-C24Va31U.js delete mode 100644 static/assets/GcodeToolpathViewer-D4M17KI8.js delete mode 100644 static/assets/GroupEditPage-i9TLWDyl.js delete mode 100644 static/assets/ImagePreviewModal-BcWeiOrd.js delete mode 100644 static/assets/InventoryPage-BWTPGneX.js delete mode 100644 static/assets/LibraryTrashPage-Ck7R9blU.js delete mode 100644 static/assets/LoginPage-CXIN3BLj.js delete mode 100644 static/assets/MaintenancePage-BAMWk5DI.js delete mode 100644 static/assets/ModelSourcesPage-C9OvAi9p.js delete mode 100644 static/assets/NotificationsPage-5GOJDRkw.js delete mode 100644 static/assets/OrbitControls-Bbqf07B-.js delete mode 100644 static/assets/OverlayFrame-BUFy7NI1.js delete mode 100644 static/assets/PdfPreviewModal-CRMSWXkI.js delete mode 100644 static/assets/PhotoGalleryModal-BxTCJXU3.js delete mode 100644 static/assets/PrinterLocationsPage-D2AqeJ9i.js delete mode 100644 static/assets/PrintersPage-zDUFDj5q.js delete mode 100644 static/assets/ProfilesPage-C0jHVdln.js delete mode 100644 static/assets/ProjectDetailPage-CaZEFrXK.js delete mode 100644 static/assets/ProjectsPage-C2aXY-5x.js delete mode 100644 static/assets/QueryClientProvider-Clbyr4Jg.js delete mode 100644 static/assets/QueuePage-DTh2aCRG.js delete mode 100644 static/assets/ReferenceLine-l3CI8D5f.js delete mode 100644 static/assets/RichTextEditor-CtyYZFiu.js delete mode 100644 static/assets/SettingsPage-DOroBYzD.js delete mode 100644 static/assets/SetupPage-hUHebIlA.js delete mode 100644 static/assets/SliceModal-DilRo4MU.js delete mode 100644 static/assets/SpoolBuddyAmsPage-CCeWMoIm.js delete mode 100644 static/assets/SpoolBuddyCalibrationPage-BPEaykbd.js delete mode 100644 static/assets/SpoolBuddyDashboard-B_0MmvgI.js delete mode 100644 static/assets/SpoolBuddyInventoryPage-BmxJPbmk.js delete mode 100644 static/assets/SpoolBuddySettingsPage-DKia6Rid.js delete mode 100644 static/assets/SpoolBuddyWriteTagPage-CMlhWgSL.js delete mode 100644 static/assets/SpoolIcon-OZKzbEc8.js delete mode 100644 static/assets/SpreadsheetPreviewModal-WUjrw07T.js delete mode 100644 static/assets/StatsPage-D0cqrND3.js delete mode 100644 static/assets/StreamOverlayPage-CA9KpVK8.css delete mode 100644 static/assets/StreamOverlayPage-DWOhiuT-.js delete mode 100644 static/assets/SystemInfoPage-ClAReSss.js delete mode 100644 static/assets/ToastContext-VFOrCCoA.js delete mode 100644 static/assets/Toggle-CwMiiK0g.js delete mode 100644 static/assets/arrow-down-C4OBNDqJ.js delete mode 100644 static/assets/arrow-left-MSgMxOFV.js delete mode 100644 static/assets/arrow-right-Da96kh4S.js delete mode 100644 static/assets/arrow-up-Dam8cLfE.js delete mode 100644 static/assets/arrow-up-down-C-1Kkt1x.js delete mode 100644 static/assets/box-BIRPxdgi.js delete mode 100644 static/assets/briefcase-BmuFBTLZ.js delete mode 100644 static/assets/calendar-DiVEdzUw.js delete mode 100644 static/assets/check-D1Pn8T1J.js delete mode 100644 static/assets/chevrons-right-OnQ-9jKz.js delete mode 100644 static/assets/chunk-aKtaBQYM.js delete mode 100644 static/assets/client-BdOyoZJz.js delete mode 100644 static/assets/cloud-off-DmnG-KIw.js delete mode 100644 static/assets/cog-pa-nvQzt.js delete mode 100644 static/assets/coins-6tqy9spE.js delete mode 100644 static/assets/columns-2-CGEUxYp_.js delete mode 100644 static/assets/copy-3N5VKzXz.js delete mode 100644 static/assets/cpu-DAX4KhDZ.js delete mode 100644 static/assets/createLucideIcon-Yo2YoYjm.js delete mode 100644 static/assets/date-G9KUauqz.js delete mode 100644 static/assets/de-DvnDPDo7.js delete mode 100644 static/assets/dollar-sign-ChtX2KHo.js delete mode 100644 static/assets/droplet-PpnaB65N.js delete mode 100644 static/assets/ellipsis-vertical-So0E77JK.js delete mode 100644 static/assets/es-DZZ0IPDC.js delete mode 100644 static/assets/eye-off-B6dY82pM.js delete mode 100644 static/assets/fan-DSlOeWAh.js delete mode 100644 static/assets/filamentSwatchHelpers-D3oXQSGk.js delete mode 100644 static/assets/file-DDkFXH-b.js delete mode 100644 static/assets/file-box-BjB1o93N.js delete mode 100644 static/assets/file-spreadsheet-8-W2zdLi.js delete mode 100644 static/assets/fr-D9McbtW3.js delete mode 100644 static/assets/funnel-BtI48m95.js delete mode 100644 static/assets/gauge-xO5PdXj0.js delete mode 100644 static/assets/git-compare-DbgnTP6k.js delete mode 100644 static/assets/grip-vertical-D4urhrwn.js delete mode 100644 static/assets/haSensorDisplay-CSo8Nw_c.js delete mode 100644 static/assets/hard-drive-CN9rq6Uq.js delete mode 100644 static/assets/history-BBZbf3sJ.js delete mode 100644 static/assets/index-BoVbGTav.js delete mode 100644 static/assets/index-jO4mF6Tu.css delete mode 100644 static/assets/info-DcT0QU8O.js delete mode 100644 static/assets/inventorySearch-yJim1_Yj.js delete mode 100644 static/assets/it-BpOecomy.js delete mode 100644 static/assets/ja-T2xC1zs8.js delete mode 100644 static/assets/ko-C1--UcOC.js delete mode 100644 static/assets/layout-grid-D4S7CgZJ.js delete mode 100644 static/assets/libraryFiles-B0B1SFfn.js delete mode 100644 static/assets/link-2-Bj1oMj3O.js delete mode 100644 static/assets/list-CBXEiN6g.js delete mode 100644 static/assets/loader-circle-CB4leTZa.js delete mode 100644 static/assets/locationSensorDefaults-rSWWVL7r.js delete mode 100644 static/assets/lock-C288ZvMI.js delete mode 100644 static/assets/log-in-D7NdqHOf.js delete mode 100644 static/assets/maximize-2-Cggm3z8J.js delete mode 100644 static/assets/minimize-2-BLq3FJZO.js delete mode 100644 static/assets/minus-D0aY9wbr.js delete mode 100644 static/assets/move-CEngqF8v.js delete mode 100644 static/assets/nl-CtZfK3Nf.js delete mode 100644 static/assets/occt-import-js-BhHfLpto.wasm delete mode 100644 static/assets/package-D9xR0eOt.js delete mode 100644 static/assets/papaparse.min-CR43Mv99.js delete mode 100644 static/assets/pause-CPKrVlsD.js delete mode 100644 static/assets/pdf-DtHaoVwF.js delete mode 100644 static/assets/pdf.worker.min-0idpi3xP.js delete mode 100644 static/assets/pdf.worker.min-DpOxPwGP.js delete mode 100644 static/assets/pdfjs/cmaps/78-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/78-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/78-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/78-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/78-RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/78-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/78ms-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/78ms-RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/83pv-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/90ms-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/90ms-RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/90msp-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/90msp-RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/90pv-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/90pv-RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Add-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Add-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Add-RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Add-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-0.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-1.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-3.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-4.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-5.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-6.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-GB1-0.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-GB1-1.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-GB1-2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-GB1-3.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-GB1-4.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-GB1-5.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-0.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-1.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-3.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-4.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-5.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-6.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Korea1-0.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Korea1-1.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Korea1-2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap delete mode 100644 static/assets/pdfjs/cmaps/B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/B5pc-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/B5pc-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/CNS-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/CNS-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/CNS1-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/CNS1-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/CNS2-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/CNS2-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/ETHK-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/ETHK-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/ETen-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/ETen-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/ETenms-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/ETenms-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Ext-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Ext-RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Ext-RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Ext-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GB-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GB-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GB-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GB-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBK-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBK-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBK2K-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBK2K-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBKp-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBKp-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBT-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBT-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBT-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBT-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBTpc-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBTpc-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBpc-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/GBpc-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKdla-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKdla-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKdlb-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKdlb-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKgccs-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKgccs-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKm314-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKm314-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKm471-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKm471-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKscs-B5-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/HKscs-B5-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Hankaku.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Hiragana.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSC-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSC-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSC-Johab-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSC-Johab-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSCms-UHC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSCms-UHC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSCpc-EUC-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/KSCpc-EUC-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Katakana.bcmap delete mode 100644 static/assets/pdfjs/cmaps/LICENSE delete mode 100644 static/assets/pdfjs/cmaps/NWP-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/NWP-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/RKSJ-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/RKSJ-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/Roman.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UCS2-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UCS2-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UTF16-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UTF16-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UTF32-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UTF32-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UTF8-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniCNS-UTF8-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UCS2-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UCS2-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UTF16-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UTF16-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UTF32-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UTF32-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UTF8-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniGB-UTF8-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UCS2-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UCS2-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UTF16-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UTF16-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UTF32-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UTF32-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UTF8-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS-UTF8-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UCS2-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UCS2-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UTF16-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UTF16-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UTF32-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UTF32-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UTF8-H.bcmap delete mode 100644 static/assets/pdfjs/cmaps/UniKS-UTF8-V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/V.bcmap delete mode 100644 static/assets/pdfjs/cmaps/WP-Symbol.bcmap delete mode 100644 static/assets/pdfjs/iccs/CGATS001Compat-v2-micro.icc delete mode 100644 static/assets/pdfjs/iccs/LICENSE delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitDingbats.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitFixed.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitFixedBold.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitFixedItalic.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitSerif.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitSerifBold.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitSerifItalic.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/FoxitSymbol.pfb delete mode 100644 static/assets/pdfjs/standard_fonts/LICENSE_FOXIT delete mode 100644 static/assets/pdfjs/standard_fonts/LICENSE_LIBERATION delete mode 100644 static/assets/pdfjs/standard_fonts/LiberationSans-Bold.ttf delete mode 100644 static/assets/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf delete mode 100644 static/assets/pdfjs/standard_fonts/LiberationSans-Italic.ttf delete mode 100644 static/assets/pdfjs/standard_fonts/LiberationSans-Regular.ttf delete mode 100644 static/assets/pdfjs/wasm/LICENSE_JBIG2 delete mode 100644 static/assets/pdfjs/wasm/LICENSE_OPENJPEG delete mode 100644 static/assets/pdfjs/wasm/LICENSE_PDFJS_JBIG2 delete mode 100644 static/assets/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG delete mode 100644 static/assets/pdfjs/wasm/LICENSE_PDFJS_QCMS delete mode 100644 static/assets/pdfjs/wasm/LICENSE_QCMS delete mode 100644 static/assets/pdfjs/wasm/jbig2.wasm delete mode 100644 static/assets/pdfjs/wasm/jbig2_nowasm_fallback.js delete mode 100644 static/assets/pdfjs/wasm/openjpeg.wasm delete mode 100644 static/assets/pdfjs/wasm/openjpeg_nowasm_fallback.js delete mode 100644 static/assets/pdfjs/wasm/qcms_bg.wasm delete mode 100644 static/assets/pdfjs/wasm/quickjs-eval.js delete mode 100644 static/assets/pdfjs/wasm/quickjs-eval.wasm delete mode 100644 static/assets/pen-line-DYnjxVtS.js delete mode 100644 static/assets/play-DROvjtg7.js delete mode 100644 static/assets/printerModel-DrNvERGQ.js delete mode 100644 static/assets/process-schema-CzfeynAH.js delete mode 100644 static/assets/process-toggle-rules-DDBax3G5.js delete mode 100644 static/assets/process-ui-tree-BWrKRLV6.js delete mode 100644 static/assets/pt-BR-CrNWoBDc.js delete mode 100644 static/assets/purify.es-F_7NWVtX.js delete mode 100644 static/assets/qr-code-PQYGpSrt.js delete mode 100644 static/assets/queueItemName-UFY7kpjG.js delete mode 100644 static/assets/react-dom-xhkz5--P.js delete mode 100644 static/assets/ru-BH6X9QDA.js delete mode 100644 static/assets/scale-lRomj4j_.js delete mode 100644 static/assets/scan-search-LN9I03Ga.js delete mode 100644 static/assets/search-BpAJdxRw.js delete mode 100644 static/assets/settings-2-DYB5IzKV.js delete mode 100644 static/assets/shield-CfkeOiiL.js delete mode 100644 static/assets/skip-forward-BE5zCMec.js delete mode 100644 static/assets/slicer-DgCA7cW6.js delete mode 100644 static/assets/slicerPrinterMatch-g7Dr6p0c.js delete mode 100644 static/assets/smartphone-DOKM2tP4.js delete mode 100644 static/assets/sortable.esm-uF6WmP46.js delete mode 100644 static/assets/sparkles-AUof3WTI.js delete mode 100644 static/assets/spoolPaint-CrvpHqdD.js delete mode 100644 static/assets/stepPreview.worker-QYcQPnWg.js delete mode 100644 static/assets/sticky-note-C75KNLfY.js delete mode 100644 static/assets/sv-BB_QxdtQ.js delete mode 100644 static/assets/target-PpftuQXz.js delete mode 100644 static/assets/timer-BYBW_ysJ.js delete mode 100644 static/assets/tr-BBzKsz9z.js delete mode 100644 static/assets/triangle-alert-gIik0G2C.js delete mode 100644 static/assets/uk-BYvqnFMj.js delete mode 100644 static/assets/unlink-DJBJ04VB.js delete mode 100644 static/assets/useCancellableTimeout-CXuQGelB.js delete mode 100644 static/assets/useMutation-qh3Iyscy.js delete mode 100644 static/assets/usePreviewFullscreen-DOsgAeT7.js delete mode 100644 static/assets/useQueries-C87lFrjE.js delete mode 100644 static/assets/useQuery-CCy0EwsM.js delete mode 100644 static/assets/utils-D0ctH_jZ.js delete mode 100644 static/assets/wind-CsHQYbhI.js delete mode 100644 static/assets/with-selector-C2KEMbwS.js delete mode 100644 static/assets/workflow-DxBrfirU.js delete mode 100644 static/assets/x-Dctff5l9.js delete mode 100644 static/assets/xlsx-CKkngM-o.js delete mode 100644 static/assets/zh-CN-zENHv8ar.js delete mode 100644 static/assets/zh-TW-BbdM61yf.js delete mode 100644 static/assets/zoom-out-C85W_TC5.js delete mode 100644 static/fonts/inter-latin-ext.woff2 delete mode 100644 static/fonts/inter-latin.woff2 delete mode 100644 static/icons/ams-ht.png delete mode 100644 static/icons/ams-settings.svg delete mode 100644 static/icons/ams-wiring-center.svg delete mode 100644 static/icons/ams-wiring-left.svg delete mode 100644 static/icons/ams-wiring-right.svg delete mode 100644 static/icons/ams.png delete mode 100644 static/icons/chamber.svg delete mode 100644 static/icons/dual-extruder-left.png delete mode 100644 static/icons/dual-extruder-right.png delete mode 100644 static/icons/dual-extruder-right_sav.png delete mode 100644 static/icons/dual-extruder.png delete mode 100644 static/icons/extruder-change-filament.png delete mode 100644 static/icons/extruder-left-right.png delete mode 100644 static/icons/eye.svg delete mode 100644 static/icons/heatbed.svg delete mode 100644 static/icons/home.svg delete mode 100644 static/icons/hotend.svg delete mode 100644 static/icons/humidity-empty.svg delete mode 100644 static/icons/humidity-full.svg delete mode 100644 static/icons/humidity-half.svg delete mode 100644 static/icons/jogpad.png delete mode 100644 static/icons/jogpad.svg delete mode 100644 static/icons/lamp.svg delete mode 100644 static/icons/micro-sd.svg delete mode 100644 static/icons/reload.svg delete mode 100644 static/icons/settings.svg delete mode 100644 static/icons/single-extruder1.png delete mode 100644 static/icons/single-extruder2.png delete mode 100644 static/icons/skip-objects.svg delete mode 100644 static/icons/snowflake.svg delete mode 100644 static/icons/speed.svg delete mode 100644 static/icons/temperature.svg delete mode 100644 static/icons/ventilation.svg delete mode 100644 static/icons/video-camera.svg delete mode 100644 static/icons/water.svg delete mode 100644 static/icons/webcam.svg delete mode 100644 static/img/android-chrome-192x192.png delete mode 100644 static/img/android-chrome-512x512.png delete mode 100644 static/img/apple-touch-icon.png delete mode 100644 static/img/bambuddy_logo_dark.png delete mode 100644 static/img/bambuddy_logo_dark_transparent.png delete mode 100644 static/img/bambuddy_logo_light.png delete mode 100644 static/img/bambuddy_logo_powered_by.png delete mode 100644 static/img/bed/bed_cool.png delete mode 100644 static/img/bed/bed_cool_supertack.png delete mode 100644 static/img/bed/bed_engineering.png delete mode 100644 static/img/bed/bed_high_templ.png delete mode 100644 static/img/bed/bed_pei.png delete mode 100644 static/img/bed/bed_pei_cool.png delete mode 100644 static/img/favicon-16x16.png delete mode 100644 static/img/favicon-32x32.png delete mode 100644 static/img/favicon.png delete mode 100644 static/img/printers/a1.png delete mode 100644 static/img/printers/a1f.png delete mode 100644 static/img/printers/a1mini.png delete mode 100644 static/img/printers/a2l.png delete mode 100644 static/img/printers/default.png delete mode 100644 static/img/printers/h2c.png delete mode 100644 static/img/printers/h2d.png delete mode 100644 static/img/printers/h2dpro.png delete mode 100644 static/img/printers/o1c.png delete mode 100644 static/img/printers/o1e.png delete mode 100644 static/img/printers/o1s.png delete mode 100644 static/img/printers/p1p.png delete mode 100644 static/img/printers/p1s.png delete mode 100644 static/img/printers/printer_placeholder.png delete mode 100644 static/img/printers/x1c.png delete mode 100644 static/img/printers/x1e.png delete mode 100644 static/img/printers/x2d.png delete mode 100644 static/img/screenshot-desktop.png delete mode 100644 static/img/screenshot-mobile.png delete mode 100644 static/img/sponsors/northpole-3d-printing.jpg delete mode 100644 static/img/sponsors/notify.png delete mode 100644 static/img/spoolbuddy_logo_dark.png delete mode 100644 static/img/spoolbuddy_logo_dark_small.png delete mode 100644 static/index.html delete mode 100644 static/manifest.json delete mode 100644 static/spoolbuddy_logo_dark.png delete mode 100644 static/sw-register.js delete mode 100644 static/sw.js delete mode 100644 static/vite.svg diff --git a/backend/app/api/routes/obico.py b/backend/app/api/routes/obico.py index 232f2d283..b0bf27701 100644 --- a/backend/app/api/routes/obico.py +++ b/backend/app/api/routes/obico.py @@ -36,7 +36,7 @@ async def get_status( "sensitivity": settings["sensitivity"], "action": settings["action"], "poll_interval": settings["poll_interval"], - "external_url_configured": bool(settings["external_url"]), + "external_url_configured": bool(settings["snapshot_base_url"]), } diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py index 5dc80d0bd..06636ffe2 100644 --- a/backend/app/schemas/settings.py +++ b/backend/app/schemas/settings.py @@ -1,6 +1,7 @@ import json import re from typing import Literal +from urllib.parse import urlparse from pydantic import BaseModel, Field, ValidationInfo, field_validator @@ -645,6 +646,9 @@ class AppSettings(BaseModel): default="", description="Self-hosted Obico ML API base URL (e.g., http://192.168.1.10:3333)", ) + bambuddy_internal_url: str = Field( + default="", description="Bambuddy Internal URL for Obico; empty uses External URL" + ) obico_ml_token: str = Field( default="", description=( @@ -864,6 +868,7 @@ class AppSettingsUpdate(BaseModel): ldap_default_group: str | None = None obico_enabled: bool | None = None obico_ml_url: str | None = None + bambuddy_internal_url: str | None = None obico_ml_token: str | None = None obico_sensitivity: str | None = None obico_action: str | None = None @@ -988,6 +993,34 @@ class AppSettingsUpdate(BaseModel): raise ValueError("obico_enabled_printers must be a JSON array of printer IDs (integers)") return v + @field_validator("bambuddy_internal_url") + @classmethod + def validate_bambuddy_internal_url(cls, v: str | None) -> str: + """Require an absolute http(s) URL, or empty to fall back to External URL. + + Obico's ML server fetches snapshots from this address, so a value + without a scheme ("bambuddy:8000") would only fail later, mid-print, + as a snapshot error. Rejecting it here surfaces the mistake on save. + + An explicit null clears the field. The settings updater stores None + as the literal "None", which would read back as an address. + """ + candidate = (v or "").strip() + if not candidate: + return "" + error = "bambuddy_internal_url must be a full http:// or https:// address" + if any(ch.isspace() for ch in candidate): + raise ValueError(error) + try: + parsed = urlparse(candidate) + hostname = parsed.hostname + _ = parsed.port # raises on a port outside 0-65535 + except ValueError: + raise ValueError(error) from None + if parsed.scheme not in ("http", "https") or not hostname: + raise ValueError(error) + return candidate + @staticmethod def _validate_preset_triple(v: str | None, field_name: str, lo: int, hi: int) -> str | None: """Validate a JSON array of exactly 3 ints in [lo, hi]. Empty = defaults.""" diff --git a/backend/app/services/obico_detection.py b/backend/app/services/obico_detection.py index fe554fbaa..89cc939e9 100644 --- a/backend/app/services/obico_detection.py +++ b/backend/app/services/obico_detection.py @@ -130,6 +130,7 @@ class ObicoDetectionService: keys = [ "obico_enabled", "obico_ml_url", + "bambuddy_internal_url", "obico_ml_token", "obico_sensitivity", "obico_action", @@ -158,7 +159,11 @@ class ObicoDetectionService: "action": rows.get("obico_action", "notify"), "poll_interval": int(rows.get("obico_poll_interval", "10")), "enabled_printers": enabled_printers, - "external_url": (rows.get("external_url") or "").rstrip("/"), + # Where Obico's ML server fetches snapshots: the Internal URL when + # set, otherwise the public External URL. + "snapshot_base_url": ( + (rows.get("bambuddy_internal_url") or "").strip() or (rows.get("external_url") or "").strip() + ).rstrip("/"), } # ---- main loop ---- @@ -297,17 +302,17 @@ class ObicoDetectionService: self._no_verdict(printer_id, f"Failed to capture snapshot for printer {printer_id}") return - external_url = settings.get("external_url") or "" - if not external_url: + snapshot_base_url = settings.get("snapshot_base_url") or "" + if not snapshot_base_url: self._no_verdict( printer_id, - "external_url setting is empty — Obico's ML API needs a reachable URL to fetch the snapshot from. " - "Set Settings → General → External URL.", + "bambuddy_internal_url and external_url settings are empty — Obico's ML API needs a reachable URL to fetch the snapshot from. " + "Set Settings → Failure Detection → Bambuddy Internal URL or Settings → Network → External URL.", ) return nonce = await stash_frame(frame) - snapshot_url = f"{external_url}/api/v1/obico/cached-frame/{nonce}" + snapshot_url = f"{snapshot_base_url}/api/v1/obico/cached-frame/{nonce}" ml_url = f"{settings['ml_url']}/p/" try: @@ -403,7 +408,7 @@ class ObicoDetectionService: ``error`` the most recent poll produced no verdict. ``error`` carries the reason — a rejected token, an unreachable ML API, a - camera that would not yield a frame, an unset External URL. + camera that would not yield a frame, a missing Bambuddy address. ``unknown`` monitored, but no inference has come back yet. The state entry is created when the print is first seen, which is before the first capture, so this is the honest answer for diff --git a/backend/tests/integration/test_obico_api.py b/backend/tests/integration/test_obico_api.py index aab7cc042..9b6a3bee3 100644 --- a/backend/tests/integration/test_obico_api.py +++ b/backend/tests/integration/test_obico_api.py @@ -5,11 +5,18 @@ pre-captured JPEG frames. This endpoint lets the detection loop sidestep Obico's hardcoded 5s read timeout by pre-populating a cache before issuing the ML call. """ +from unittest.mock import AsyncMock, MagicMock, patch + import pytest -from httpx import AsyncClient +from httpx import AsyncClient, Request, Response from backend.app.core.printer_scope import ALL_PRINTERS -from backend.app.services.obico_detection import _frame_cache, obico_detection_service, stash_frame +from backend.app.services.obico_detection import ( + ObicoDetectionService, + _frame_cache, + obico_detection_service, + stash_frame, +) from backend.app.services.obico_smoothing import PrintState FAKE_JPEG = b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xd9" @@ -73,6 +80,103 @@ class TestObicoCachedFrame: assert "no-store" in response.headers.get("cache-control", "") +class TestBambuddyInternalUrl: + @pytest.mark.asyncio + @pytest.mark.integration + @pytest.mark.parametrize( + ("bambuddy_internal_url", "external_url", "expected_base"), + [ + (None, "https://bambuddy.example.com", "https://bambuddy.example.com"), + ("", "https://bambuddy.example.com/", "https://bambuddy.example.com"), + ("http://bambuddy:8000/", "https://bambuddy.example.com", "http://bambuddy:8000"), + ("http://192.168.1.20:8000", "", "http://192.168.1.20:8000"), + ], + ) + async def test_saved_url_controls_snapshot_callback( + self, async_client: AsyncClient, bambuddy_internal_url, external_url, expected_base + ): + updates = {"external_url": external_url, "obico_ml_url": "http://obico:3333"} + if bambuddy_internal_url is not None: + updates["bambuddy_internal_url"] = bambuddy_internal_url + response = await async_client.put("/api/v1/settings/", json=updates) + assert response.status_code == 200 + saved = (await async_client.get("/api/v1/settings/")).json() + assert saved["external_url"] == external_url + assert saved["bambuddy_internal_url"] == (bambuddy_internal_url or "") + status = (await async_client.get("/api/v1/obico/status")).json() + assert status["external_url_configured"] is True + + async def fetch_snapshot(url, *, params, headers): + assert url == "http://obico:3333/p/" + assert params["img"].startswith(f"{expected_base}/api/v1/obico/cached-frame/") + frame = await async_client.get(params["img"]) + assert frame.status_code == 200 + assert frame.content == FAKE_JPEG + return Response(200, json={"detections": []}, request=Request("GET", url)) + + svc = ObicoDetectionService() + settings = await svc._load_settings() + mock_client = MagicMock() + mock_client.get = AsyncMock(side_effect=fetch_snapshot) + mock_client.__aenter__ = AsyncMock(return_value=mock_client) + mock_client.__aexit__ = AsyncMock(return_value=False) + with ( + patch("backend.app.services.obico_detection.httpx.AsyncClient", return_value=mock_client), + patch.object(svc, "_capture_frame", new=AsyncMock(return_value=FAKE_JPEG)), + ): + await svc._check_printer(1, MagicMock(state="RUNNING", task_name="job", subtask_name=""), settings) + mock_client.get.assert_awaited_once() + assert svc.get_per_printer()[1]["class"] == "safe" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_empty_value_clears_the_bambuddy_internal_url(self, async_client: AsyncClient): + response = await async_client.put( + "/api/v1/settings/", + json={"bambuddy_internal_url": "http://bambuddy:8000", "external_url": "https://bambuddy.example.com"}, + ) + assert response.status_code == 200 + response = await async_client.put("/api/v1/settings/", json={"bambuddy_internal_url": ""}) + assert response.status_code == 200 + assert response.json()["bambuddy_internal_url"] == "" + assert response.json()["external_url"] == "https://bambuddy.example.com" + assert (await ObicoDetectionService()._load_settings())["snapshot_base_url"] == "https://bambuddy.example.com" + + @pytest.mark.asyncio + @pytest.mark.integration + @pytest.mark.parametrize( + "value", + ["bambuddy:8000", "192.168.1.20:8000", "ftp://bambuddy", "http://", "http://bam buddy:8000", "http://h:99999"], + ) + async def test_address_without_http_scheme_is_rejected(self, async_client: AsyncClient, value): + response = await async_client.put("/api/v1/settings/", json={"bambuddy_internal_url": value}) + assert response.status_code == 422 + saved = (await async_client.get("/api/v1/settings/")).json() + assert saved["bambuddy_internal_url"] == "" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_null_clears_instead_of_storing_none(self, async_client: AsyncClient): + await async_client.put( + "/api/v1/settings/", + json={"bambuddy_internal_url": "http://bambuddy:8000", "external_url": "https://bambuddy.example.com"}, + ) + response = await async_client.put("/api/v1/settings/", json={"bambuddy_internal_url": None}) + assert response.status_code == 200 + assert response.json()["bambuddy_internal_url"] == "" + assert (await ObicoDetectionService()._load_settings())["snapshot_base_url"] == "https://bambuddy.example.com" + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_surrounding_whitespace_is_stripped(self, async_client: AsyncClient): + response = await async_client.put( + "/api/v1/settings/", json={"bambuddy_internal_url": " http://bambuddy:8000/ "} + ) + assert response.status_code == 200 + assert response.json()["bambuddy_internal_url"] == "http://bambuddy:8000/" + assert (await ObicoDetectionService()._load_settings())["snapshot_base_url"] == "http://bambuddy:8000" + + class TestObicoPrinterStatus: """The lightweight /obico/printer-status endpoint for printer-card badges (#1546).""" diff --git a/backend/tests/unit/test_obico_detection.py b/backend/tests/unit/test_obico_detection.py index 3b21e17c1..e93a57893 100644 --- a/backend/tests/unit/test_obico_detection.py +++ b/backend/tests/unit/test_obico_detection.py @@ -161,7 +161,7 @@ class TestMlApiToken: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } base.update(overrides) return base @@ -354,7 +354,7 @@ class TestPollOneStateLifecycle: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } status = MagicMock(state="RUNNING", task_name="new_task", subtask_name="") @@ -387,7 +387,7 @@ class TestPollOneStateLifecycle: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } status = MagicMock(state="RUNNING", task_name="job", subtask_name="") @@ -416,7 +416,7 @@ class TestPollOneStateLifecycle: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } status = MagicMock(state="RUNNING", task_name="job", subtask_name="") @@ -451,7 +451,7 @@ class TestPollOneStateLifecycle: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } status = MagicMock(state="RUNNING", task_name="job", subtask_name="") @@ -669,7 +669,7 @@ class TestCheckPrinterUsesCachedFrameUrl: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } status = MagicMock(state="RUNNING", task_name="job", subtask_name="") @@ -709,7 +709,7 @@ class TestCheckPrinterUsesCachedFrameUrl: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } status = MagicMock(state="RUNNING", task_name="job", subtask_name="") @@ -729,8 +729,8 @@ class TestCheckPrinterUsesCachedFrameUrl: assert "Failed to capture snapshot" in svc._last_error @pytest.mark.asyncio - async def test_missing_external_url_skips_ml_call(self): - """Without external_url, Obico can't reach our cached-frame endpoint.""" + async def test_missing_snapshot_address_skips_ml_call(self): + """Without a snapshot address, Obico can't reach our cached-frame endpoint.""" svc = ObicoDetectionService() settings = { "enabled": True, @@ -739,7 +739,7 @@ class TestCheckPrinterUsesCachedFrameUrl: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "", + "snapshot_base_url": "", } status = MagicMock(state="RUNNING", task_name="job", subtask_name="") @@ -756,7 +756,7 @@ class TestCheckPrinterUsesCachedFrameUrl: mock_client.get.assert_not_called() assert svc._last_error is not None - assert "external_url" in svc._last_error + assert "bambuddy_internal_url" in svc._last_error @pytest.mark.asyncio async def test_successful_cycle_clears_previous_error(self): @@ -774,7 +774,7 @@ class TestCheckPrinterUsesCachedFrameUrl: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } status = MagicMock(state="RUNNING", task_name="job", subtask_name="") @@ -822,7 +822,7 @@ class TestNoVerdictIsNotSafe: "action": "notify", "poll_interval": 10, "enabled_printers": None, - "external_url": "http://bambuddy:8000", + "snapshot_base_url": "http://bambuddy:8000", } @staticmethod @@ -882,9 +882,9 @@ class TestNoVerdictIsNotSafe: assert "capture" in entry["error"].lower() @pytest.mark.asyncio - async def test_missing_external_url_reports_error(self): + async def test_missing_snapshot_address_reports_error(self): svc = ObicoDetectionService() - settings = {**self.SETTINGS, "external_url": ""} + settings = {**self.SETTINGS, "snapshot_base_url": ""} with patch.object(svc, "_capture_frame", new=AsyncMock(return_value=FAKE_JPEG)): await svc._check_printer(1, self._status(), settings) diff --git a/backend/tests/unit/test_outbound_url_ssrf_guards.py b/backend/tests/unit/test_outbound_url_ssrf_guards.py index 41d00c826..8b958055d 100644 --- a/backend/tests/unit/test_outbound_url_ssrf_guards.py +++ b/backend/tests/unit/test_outbound_url_ssrf_guards.py @@ -253,13 +253,14 @@ def test_every_url_setting_is_either_guarded_or_explicitly_exempt(): added per-incident rather than to the whole class of fields. """ exempt = { - # Bambuddy's own public address, not a destination it requests. It is + # Bambuddy's own address, not a destination it requests. It is # rendered into notification bodies and OIDC redirect URIs, and handed # to Obico's ML server as the `img` parameter for that server to fetch # (obico_detection.py builds `{external_url}/api/v1/obico/cached-frame/ # {nonce}`). Pointing it at a private address only breaks Bambuddy's own # links; it cannot make Bambuddy request anything it otherwise wouldn't. "external_url", + "bambuddy_internal_url", # Guarded by assert_safe_spoolman_url at each consumer (spoolman.py, # location_service.py, inventory.py, spoolbuddy.py, # spoolman_inventory.py) rather than in the schema, keeping its @@ -609,6 +610,7 @@ GUARDED_BODY_URLS = { # Not a destination Bambuddy requests — no guard applies. NOT_A_FETCH_TARGET = { ("AppSettingsUpdate", "external_url"), # Bambuddy's own address (see exempt list above) + ("AppSettingsUpdate", "bambuddy_internal_url"), ("AppSettingsUpdate", "ldap_server_url"), # ldap://, handed to an LDAP client ("ProjectCreate", "url"), # stored link, rendered in the UI, never fetched ("ProjectUpdate", "url"), diff --git a/frontend/src/__tests__/components/FailureDetectionSettings.test.tsx b/frontend/src/__tests__/components/FailureDetectionSettings.test.tsx index 4ba8bfaf6..70604f080 100644 --- a/frontend/src/__tests__/components/FailureDetectionSettings.test.tsx +++ b/frontend/src/__tests__/components/FailureDetectionSettings.test.tsx @@ -23,6 +23,7 @@ const baseSettings = { include_beta_updates: false, obico_enabled: false, obico_ml_url: '', + bambuddy_internal_url: '', obico_ml_token: '', obico_sensitivity: 'medium', obico_action: 'notify', @@ -60,6 +61,7 @@ describe('FailureDetectionSettings', () => { }); expect(screen.getByText(/Obico ML API URL/i)).toBeInTheDocument(); expect(screen.getByText(/Sensitivity/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText('http://bambuddy:8000')).toBeDisabled(); }); it('test button calls the test-connection endpoint and shows success', async () => { @@ -84,6 +86,53 @@ describe('FailureDetectionSettings', () => { expect(await screen.findByText(/ML API reachable/i)).toBeInTheDocument(); }); + it.each([ + ['', 'http://print-server:8000'], + ['http://print-server:8000', ''], + ])('auto-saves a Bambuddy Internal URL change from "%s" to "%s"', async (initial, next) => { + let settings = { ...baseSettings, obico_enabled: true, bambuddy_internal_url: initial }; + let saved: Record | null = null; + server.use( + http.get('/api/v1/settings/', () => HttpResponse.json(settings)), + http.put('/api/v1/settings/', async ({ request }) => { + saved = (await request.json()) as Record; + settings = { ...settings, ...saved }; + return HttpResponse.json(settings); + }), + ); + render(); + const input = await screen.findByPlaceholderText('http://bambuddy:8000'); + await waitFor(() => { + expect(input).not.toBeDisabled(); + expect(input).toHaveValue(initial); + }); + await userEvent.clear(input); + if (next) await userEvent.type(input, next); + await waitFor(() => expect(saved?.bambuddy_internal_url).toBe(next), { timeout: 3000 }); + }); + + it('flags a Bambuddy Internal URL without a scheme and does not save it', async () => { + const puts: Record[] = []; + server.use( + http.get('/api/v1/settings/', () => + HttpResponse.json({ ...baseSettings, obico_enabled: true, bambuddy_internal_url: '' }), + ), + http.put('/api/v1/settings/', async ({ request }) => { + const body = (await request.json()) as Record; + puts.push(body); + return HttpResponse.json({ ...baseSettings, ...body }); + }), + ); + render(); + const input = await screen.findByPlaceholderText('http://bambuddy:8000'); + await waitFor(() => expect(input).not.toBeDisabled()); + await userEvent.type(input, 'bambuddy:8000'); + expect(await screen.findByText(/Must start with http:\/\/ or https:\/\//)).toBeInTheDocument(); + expect(input).toHaveAttribute('aria-invalid', 'true'); + await new Promise((resolve) => setTimeout(resolve, 800)); + expect(puts.filter((body) => 'bambuddy_internal_url' in body)).toEqual([]); + }); + describe('ML API token (#2733)', () => { const enabledWithToken = { ...baseSettings, diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 15bb95739..dedb5cbf0 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1590,6 +1590,7 @@ export interface AppSettings { ldap_default_group: string; obico_enabled: boolean; obico_ml_url: string; + bambuddy_internal_url: string; obico_ml_token: string; obico_sensitivity: 'low' | 'medium' | 'high'; obico_action: 'notify' | 'pause' | 'pause_and_off'; diff --git a/frontend/src/components/FailureDetectionSettings.tsx b/frontend/src/components/FailureDetectionSettings.tsx index b22e5fbdf..befb43283 100644 --- a/frontend/src/components/FailureDetectionSettings.tsx +++ b/frontend/src/components/FailureDetectionSettings.tsx @@ -11,6 +11,20 @@ import { NumberInput } from './NumberInput'; type TestResult = { ok: boolean; message: string } | null; +// The ML server fetches snapshots from this address, so it needs a scheme. +// Empty falls back to External URL. Mirrors the backend validator, so a value +// accepted here is never refused on save. +function isValidInternalUrl(value: string): boolean { + const trimmed = value.trim(); + if (trimmed === '') return true; + if (/\s/.test(trimmed) || !/^https?:\/\/[^\s/?#]/i.test(trimmed)) return false; + try { + return new URL(trimmed).hostname !== ''; + } catch { + return false; + } +} + export function FailureDetectionSettings() { const { t } = useTranslation(); const queryClient = useQueryClient(); @@ -18,6 +32,7 @@ export function FailureDetectionSettings() { const [enabled, setEnabled] = useState(false); const [mlUrl, setMlUrl] = useState(''); + const [bambuddyInternalUrl, setBambuddyInternalUrl] = useState(''); const [mlToken, setMlToken] = useState(''); const [sensitivity, setSensitivity] = useState<'low' | 'medium' | 'high'>('medium'); const [action, setAction] = useState<'notify' | 'pause' | 'pause_and_off'>('notify'); @@ -25,6 +40,7 @@ export function FailureDetectionSettings() { const [enabledPrinters, setEnabledPrinters] = useState(null); // null = all const [testResult, setTestResult] = useState(null); const [initialized, setInitialized] = useState(false); + const internalUrlValid = isValidInternalUrl(bambuddyInternalUrl); const { data: settings } = useQuery({ queryKey: ['settings'], @@ -46,6 +62,7 @@ export function FailureDetectionSettings() { if (!settings) return; setEnabled(settings.obico_enabled ?? false); setMlUrl(settings.obico_ml_url ?? ''); + setBambuddyInternalUrl(settings.bambuddy_internal_url ?? ''); setMlToken(settings.obico_ml_token ?? ''); setSensitivity(settings.obico_sensitivity ?? 'medium'); setAction(settings.obico_action ?? 'notify'); @@ -66,6 +83,9 @@ export function FailureDetectionSettings() { api.updateSettings({ obico_enabled: enabled, obico_ml_url: mlUrl, + // An invalid address is left out so the other fields still save; the + // field shows why it was not applied. + ...(internalUrlValid ? { bambuddy_internal_url: bambuddyInternalUrl.trim() } : {}), obico_ml_token: mlToken, obico_sensitivity: sensitivity, obico_action: action, @@ -87,13 +107,14 @@ export function FailureDetectionSettings() { return ( settings.obico_enabled !== enabled || settings.obico_ml_url !== mlUrl || + (internalUrlValid && (settings.bambuddy_internal_url ?? '') !== bambuddyInternalUrl.trim()) || (settings.obico_ml_token ?? '') !== mlToken || settings.obico_sensitivity !== sensitivity || settings.obico_action !== action || settings.obico_poll_interval !== pollInterval || settings.obico_enabled_printers !== (enabledPrinters === null ? '' : JSON.stringify(enabledPrinters)) ); - }, [settings, initialized, enabled, mlUrl, mlToken, sensitivity, action, pollInterval, enabledPrinters]); + }, [settings, initialized, enabled, mlUrl, bambuddyInternalUrl, internalUrlValid, mlToken, sensitivity, action, pollInterval, enabledPrinters]); // Auto-save on change (debounced) useEffect(() => { @@ -101,7 +122,7 @@ export function FailureDetectionSettings() { const id = setTimeout(() => saveMutation.mutate(), 500); return () => clearTimeout(id); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [hasUnsavedChanges, enabled, mlUrl, mlToken, sensitivity, action, pollInterval, enabledPrinters]); + }, [hasUnsavedChanges, enabled, mlUrl, bambuddyInternalUrl, mlToken, sensitivity, action, pollInterval, enabledPrinters]); const handleTest = async () => { setTestResult(null); @@ -215,6 +236,27 @@ export function FailureDetectionSettings() { )} +
+ + setBambuddyInternalUrl(e.target.value)} + placeholder="http://bambuddy:8000" + className="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-white text-sm" + disabled={!enabled} + aria-invalid={!internalUrlValid} + /> + {!internalUrlValid && ( +

+ {t('failureDetection.bambuddyInternalUrlInvalid')} +

+ )} +

{t('failureDetection.bambuddyInternalUrlHint')}

+
+