瀏覽代碼

Apply the reprint checks to re-slicing an archive with the built-in slicer.

maziggy 1 周之前
父節點
當前提交
72dfa7b168
共有 57 個文件被更改,包括 59 次插入 和 6 次删除
  1. 5 0
      backend/app/api/routes/archives.py
  2. 31 0
      backend/tests/integration/test_queue_source_gate.py
  3. 20 1
      frontend/src/pages/ArchivesPage.tsx
  4. 0 0
      static/assets/AdditionalSection-BqbDeLgV.js
  5. 0 0
      static/assets/AmsUnitCard-U88OvkEO.js
  6. 0 0
      static/assets/ArchivesPage-BGKYZXGq.js
  7. 0 0
      static/assets/ArchivesPage-CDs9z91g.js
  8. 0 0
      static/assets/AssignToAmsModal-ipD52LBY.js
  9. 0 0
      static/assets/CamWallPage-BHCpO_aB.js
  10. 0 0
      static/assets/CameraDiagnoseModal-BEO7dA5T.js
  11. 0 0
      static/assets/CameraPage-DPAwYOmo.js
  12. 0 0
      static/assets/CameraWall-CLj-LAfe.js
  13. 0 0
      static/assets/ColumnConfigModal-Dqk-39_2.js
  14. 0 0
      static/assets/ConfigureAmsSlotModal-DuA7W30-.js
  15. 0 0
      static/assets/ConnectAuthorizePage-C1yBhEha.js
  16. 0 0
      static/assets/ContextMenu-DUJeDIHr.js
  17. 0 0
      static/assets/ExternalLinkPage-CIq1RQNM.js
  18. 0 1
      static/assets/FileManagerPage-DZH_Bw6l.js
  19. 0 0
      static/assets/FileUploadModal-BAhUBTVv.js
  20. 0 0
      static/assets/FinancePage-CYZhWelI.js
  21. 0 0
      static/assets/GCodeViewerPage-DAecPqxY.js
  22. 0 0
      static/assets/GroupEditPage-BtyNUNhx.js
  23. 0 0
      static/assets/ImagePreviewModal-F4bZnC-Z.js
  24. 0 0
      static/assets/InventoryPage-a4JeR13h.js
  25. 0 0
      static/assets/LibraryTrashPage-C4xbwSNb.js
  26. 0 0
      static/assets/LoginPage-BlwnmIa9.js
  27. 0 0
      static/assets/MaintenancePage-DmnqvBa3.js
  28. 0 0
      static/assets/ModelSourcesPage-tbH4iQIS.js
  29. 0 0
      static/assets/NotificationsPage-80sFCW1r.js
  30. 0 1
      static/assets/PdfPreviewModal-DWcU7fvp.js
  31. 0 0
      static/assets/PhotoGalleryModal-ewWFMvjN.js
  32. 0 0
      static/assets/PrinterLocationsPage-BjgcoqNp.js
  33. 0 0
      static/assets/PrintersPage-uey389h1.js
  34. 0 0
      static/assets/ProfilesPage-C8adTvkl.js
  35. 0 0
      static/assets/ProjectDetailPage-LknPNO0C.js
  36. 0 0
      static/assets/ProjectsPage-ChSMPlmO.js
  37. 0 0
      static/assets/QueuePage-BW1lvaDs.js
  38. 0 0
      static/assets/RichTextEditor-Dv032Dph.js
  39. 0 0
      static/assets/SettingsPage-Bq9_vXLl.js
  40. 0 0
      static/assets/SetupPage-CC0FVSam.js
  41. 0 0
      static/assets/SliceModal-BbLC4O59.js
  42. 0 0
      static/assets/SpoolBuddyAmsPage-yooqgJ_J.js
  43. 0 0
      static/assets/SpoolBuddyCalibrationPage-FMoSR7KC.js
  44. 0 0
      static/assets/SpoolBuddyDashboard-BY8Lu8Fx.js
  45. 0 0
      static/assets/SpoolBuddyInventoryPage-R1_p-x6c.js
  46. 1 1
      static/assets/SpoolBuddySettingsPage-DAgLwyOv.js
  47. 0 0
      static/assets/SpoolBuddyWriteTagPage-CN54tpM8.js
  48. 0 0
      static/assets/SpreadsheetPreviewModal-ENlQWYCQ.js
  49. 0 0
      static/assets/StatsPage-BuMT4J0R.js
  50. 0 0
      static/assets/StreamOverlayPage-BGc94pkS.js
  51. 0 0
      static/assets/SystemInfoPage-BNOM5owF.js
  52. 0 0
      static/assets/index-CELnCUsn.js
  53. 1 1
      static/assets/inventorySearch-B2RXCD2R.js
  54. 0 0
      static/assets/locationSensorDefaults-DpV3Hky6.js
  55. 0 0
      static/assets/pdf-C1ZSVY3h.js
  56. 0 0
      static/assets/utils-BmO3uKCd.js
  57. 1 1
      static/index.html

+ 5 - 0
backend/app/api/routes/archives.py

@@ -5014,6 +5014,11 @@ async def slice_archive(
     # (RequestActor); only auth off keeps can_read_all=True.
     can_read_all = actor is None or actor.has_permission(Permission.ARCHIVES_READ_ALL.value)
     archive = _ensure_archive_visible(archive, actor, can_read_all, printer_scope)
+    # The re-sliced copy belongs to the caller and can be queued as their own,
+    # so slicing an archive needs the same right as printing it again.
+    from backend.app.api.routes.print_queue import _assert_can_queue_archive
+
+    _assert_can_queue_archive(archive, actor, printer_scope)
 
     src_relative = archive.source_3mf_path or archive.file_path
     if not src_relative:

+ 31 - 0
backend/tests/integration/test_queue_source_gate.py

@@ -296,3 +296,34 @@ async def test_running_a_pipeline_on_an_archive_needs_the_right_to_reprint_it(
     assert refused.status_code == 403, refused.text
     # Past the gate, the run stops at the archive file this test does not create.
     assert (passed_gate.status_code, passed_gate.json()["detail"]) == (404, "Archive source file missing on disk")
+
+
+async def test_slicing_an_archive_needs_the_right_to_reprint_it(
+    async_client: AsyncClient, printer_factory, archive_factory
+):
+    # The sliced copy belongs to whoever slices it and can be queued as their own.
+    printer = await printer_factory()
+    admin = await _admin_token(async_client)
+    archive = await archive_factory(printer.id)
+    base = ["printers:read", "queue:create", "archives:read_all", "library:upload"]
+    without = _auth(
+        (await _user(async_client, admin, "without", [await _group(async_client, admin, "g1", permissions=base)]))[0]
+    )
+    with_reprint = _auth(
+        (
+            await _user(
+                async_client,
+                admin,
+                "with",
+                [await _group(async_client, admin, "g2", permissions=[*base, "archives:reprint_all"])],
+            )
+        )[0]
+    )
+    body = {"printer_preset_id": 1, "process_preset_id": 2, "filament_preset_id": 3}
+
+    refused = await async_client.post(f"/api/v1/archives/{archive.id}/slice", headers=without, json=body)
+    passed_gate = await async_client.post(f"/api/v1/archives/{archive.id}/slice", headers=with_reprint, json=body)
+
+    assert refused.status_code == 403, refused.text
+    # Past the gate, the request stops at the archive file this test does not create.
+    assert (passed_gate.status_code, passed_gate.json()["detail"]) == (404, "Archive source file missing on disk")

+ 20 - 1
frontend/src/pages/ArchivesPage.tsx

@@ -597,6 +597,12 @@ function ArchiveCard({
             openInSlicerWithToken(archive.id, filename, 'file', preferredSlicer);
           }
         },
+        // The sliced copy is printable as the user's own, so it needs the right to reprint.
+        disabled: useSlicerApi && !canModify('archives', 'reprint', archive.created_by_id),
+        title:
+          useSlicerApi && !canModify('archives', 'reprint', archive.created_by_id)
+            ? t('archives.permission.noReprint')
+            : undefined,
       },
       // Run-with-pipeline (#1425 PR B follow-up). Sources from archive's
       // source 3MF (or file_path fallback). Only when slicer-api is on.
@@ -1446,7 +1452,14 @@ function ArchiveCard({
                   openInSlicerWithToken(archive.id, filename, 'file', preferredSlicer);
                 }
               }}
-              title={useSlicerApi ? t('slice.title') : t('archives.card.openInBambuStudioToSlice')}
+              disabled={useSlicerApi && !canModify('archives', 'reprint', archive.created_by_id)}
+              title={
+                useSlicerApi
+                  ? canModify('archives', 'reprint', archive.created_by_id)
+                    ? t('slice.title')
+                    : t('archives.permission.noReprint')
+                  : t('archives.card.openInBambuStudioToSlice')
+              }
             >
               {useSlicerApi ? (
                 <Cog className="w-3 h-3 flex-shrink-0" />
@@ -2102,6 +2115,12 @@ function ArchiveListRow({
             openInSlicerWithToken(archive.id, filename, 'file', preferredSlicer);
           }
         },
+        // The sliced copy is printable as the user's own, so it needs the right to reprint.
+        disabled: useSlicerApi && !canModify('archives', 'reprint', archive.created_by_id),
+        title:
+          useSlicerApi && !canModify('archives', 'reprint', archive.created_by_id)
+            ? t('archives.permission.noReprint')
+            : undefined,
       },
       // Run-with-pipeline (#1425 PR B follow-up). Sources from archive's
       // source 3MF (or file_path fallback). Only when slicer-api is on.

File diff suppressed because it is too large
+ 0 - 0
static/assets/AdditionalSection-BqbDeLgV.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/AmsUnitCard-U88OvkEO.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ArchivesPage-BGKYZXGq.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ArchivesPage-CDs9z91g.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/AssignToAmsModal-ipD52LBY.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/CamWallPage-BHCpO_aB.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/CameraDiagnoseModal-BEO7dA5T.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/CameraPage-DPAwYOmo.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/CameraWall-CLj-LAfe.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ColumnConfigModal-Dqk-39_2.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ConfigureAmsSlotModal-DuA7W30-.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ConnectAuthorizePage-C1yBhEha.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ContextMenu-DUJeDIHr.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ExternalLinkPage-CIq1RQNM.js


File diff suppressed because it is too large
+ 0 - 1
static/assets/FileManagerPage-DZH_Bw6l.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/FileUploadModal-BAhUBTVv.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/FinancePage-CYZhWelI.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/GCodeViewerPage-DAecPqxY.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/GroupEditPage-BtyNUNhx.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ImagePreviewModal-F4bZnC-Z.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/InventoryPage-a4JeR13h.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/LibraryTrashPage-C4xbwSNb.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/LoginPage-BlwnmIa9.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/MaintenancePage-DmnqvBa3.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ModelSourcesPage-tbH4iQIS.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/NotificationsPage-80sFCW1r.js


File diff suppressed because it is too large
+ 0 - 1
static/assets/PdfPreviewModal-DWcU7fvp.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/PhotoGalleryModal-ewWFMvjN.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/PrinterLocationsPage-BjgcoqNp.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/PrintersPage-uey389h1.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ProfilesPage-C8adTvkl.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ProjectDetailPage-LknPNO0C.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ProjectsPage-ChSMPlmO.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/QueuePage-BW1lvaDs.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/RichTextEditor-Dv032Dph.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SettingsPage-Bq9_vXLl.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SetupPage-CC0FVSam.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SliceModal-BbLC4O59.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SpoolBuddyAmsPage-yooqgJ_J.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SpoolBuddyCalibrationPage-FMoSR7KC.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SpoolBuddyDashboard-BY8Lu8Fx.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SpoolBuddyInventoryPage-R1_p-x6c.js


File diff suppressed because it is too large
+ 1 - 1
static/assets/SpoolBuddySettingsPage-DAgLwyOv.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SpoolBuddyWriteTagPage-CN54tpM8.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SpreadsheetPreviewModal-ENlQWYCQ.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/StatsPage-BuMT4J0R.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/StreamOverlayPage-BGc94pkS.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SystemInfoPage-BNOM5owF.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-CELnCUsn.js


+ 1 - 1
static/assets/inventorySearch-DhdOL4y3.js → static/assets/inventorySearch-B2RXCD2R.js

@@ -1 +1 @@
-import{ft as e}from"./index-dx8-YkAB.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};
+import{ft as e}from"./index-CELnCUsn.js";function t(t,n){let r=n.trim().toLowerCase();if(!r)return!0;let i=r.match(/^#(\d+)$/);return i?t.id===Number(i[1]):String(t.id).includes(r)||t.material.toLowerCase().includes(r)||(t.brand?.toLowerCase().includes(r)??!1)||(t.color_name?.toLowerCase().includes(r)??!1)||(e(t.color_name,t.rgba,t.color_name_is_synthesized)?.toLowerCase().includes(r)??!1)||(t.subtype?.toLowerCase().includes(r)??!1)||(t.note?.toLowerCase().includes(r)??!1)||(t.slicer_filament_name?.toLowerCase().includes(r)??!1)||(t.storage_location?.toLowerCase().includes(r)??!1)||(t.material_number?.toLowerCase().includes(r)??!1)||(t.suppliers?.some(e=>e.supplier_name.toLowerCase().includes(r)||(e.supplier_article_number?.toLowerCase().includes(r)??!1))??!1)}function n(e,n){return n?e.filter(e=>t(e,n)):e}export{n as t};

File diff suppressed because it is too large
+ 0 - 0
static/assets/locationSensorDefaults-DpV3Hky6.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/pdf-C1ZSVY3h.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/utils-BmO3uKCd.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-dx8-YkAB.js"></script>
+    <script type="module" crossorigin src="/assets/index-CELnCUsn.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/chunk-aKtaBQYM.js">
     <link rel="modulepreload" crossorigin href="/assets/client-C3LdD0iY.js">
     <link rel="modulepreload" crossorigin href="/assets/QueryClientProvider-DzWmuIjj.js">

Some files were not shown because too many files changed in this diff