plexora 0.0.7__tar.gz → 0.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. {plexora-0.0.7/plexora.egg-info → plexora-0.0.8}/PKG-INFO +1 -1
  2. {plexora-0.0.7 → plexora-0.0.8}/plexora/askpass.py +27 -1
  3. {plexora-0.0.7 → plexora-0.0.8}/plexora/cli.py +21 -0
  4. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/css/main.css +82 -0
  5. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/css/settings.css +66 -0
  6. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/connectionModal.js +87 -10
  7. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/remoteGlobe.js +9 -0
  8. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/remoteState.js +13 -2
  9. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/settingsPage.js +18 -2
  10. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/base.html +4 -4
  11. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/settings.html +26 -4
  12. {plexora-0.0.7 → plexora-0.0.8}/plexora/connect.py +366 -10
  13. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/__init__.py +100 -100
  14. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/data_model.py +53 -5
  15. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/nodes.py +53 -0
  16. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/recipes.py +51 -24
  17. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/remote_sessions.py +160 -9
  18. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/remotes.py +26 -1
  19. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/node/api.py +92 -4
  20. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/providers/__init__.py +21 -0
  21. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/providers/node.py +33 -4
  22. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/settings_routes.py +48 -1
  23. {plexora-0.0.7 → plexora-0.0.8/plexora.egg-info}/PKG-INFO +1 -1
  24. {plexora-0.0.7 → plexora-0.0.8}/plexora.egg-info/SOURCES.txt +1 -0
  25. {plexora-0.0.7 → plexora-0.0.8}/pyproject.toml +1 -1
  26. {plexora-0.0.7 → plexora-0.0.8}/tests/test_cli.py +16 -0
  27. {plexora-0.0.7 → plexora-0.0.8}/tests/test_connect.py +212 -1
  28. {plexora-0.0.7 → plexora-0.0.8}/tests/test_connection_modal.py +44 -0
  29. plexora-0.0.8/tests/test_node_reconnect.py +255 -0
  30. {plexora-0.0.7 → plexora-0.0.8}/tests/test_node_warm_and_cache.py +174 -0
  31. {plexora-0.0.7 → plexora-0.0.8}/tests/test_recipes.py +23 -7
  32. {plexora-0.0.7 → plexora-0.0.8}/tests/test_remote_connect.py +284 -0
  33. {plexora-0.0.7 → plexora-0.0.8}/tests/test_settings_remotes_page.py +109 -0
  34. {plexora-0.0.7 → plexora-0.0.8}/LICENSE +0 -0
  35. {plexora-0.0.7 → plexora-0.0.8}/MANIFEST.in +0 -0
  36. {plexora-0.0.7 → plexora-0.0.8}/README.md +0 -0
  37. {plexora-0.0.7 → plexora-0.0.8}/plexora/__init__.py +0 -0
  38. {plexora-0.0.7 → plexora-0.0.8}/plexora/__main__.py +0 -0
  39. {plexora-0.0.7 → plexora-0.0.8}/plexora/_resources.py +0 -0
  40. {plexora-0.0.7 → plexora-0.0.8}/plexora/_url.py +0 -0
  41. {plexora-0.0.7 → plexora-0.0.8}/plexora/api/__init__.py +0 -0
  42. {plexora-0.0.7 → plexora-0.0.8}/plexora/api/dataset.py +0 -0
  43. {plexora-0.0.7 → plexora-0.0.8}/plexora/api/http.py +0 -0
  44. {plexora-0.0.7 → plexora-0.0.8}/plexora/api/plugin.py +0 -0
  45. {plexora-0.0.7 → plexora-0.0.8}/plexora/api/store.py +0 -0
  46. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/354_bundle.js +0 -0
  47. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/354_bundle.js.LICENSE.txt +0 -0
  48. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/418_bundle.js +0 -0
  49. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/418_bundle.js.LICENSE.txt +0 -0
  50. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +0 -0
  51. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +0 -0
  52. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/770_bundle.js +0 -0
  53. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +0 -0
  54. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/vendor_bundle.js +0 -0
  55. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/dist/vendor_bundle.js.LICENSE.txt +0 -0
  56. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +0 -0
  57. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +0 -0
  58. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +0 -0
  59. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +0 -0
  60. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +0 -0
  61. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
  62. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
  63. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
  64. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
  65. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
  66. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
  67. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
  68. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
  69. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +0 -0
  70. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
  71. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
  72. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
  73. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
  74. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
  75. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
  76. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
  77. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
  78. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
  79. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
  80. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
  81. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
  82. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
  83. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
  84. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
  85. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
  86. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
  87. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
  88. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
  89. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
  90. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
  91. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
  92. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
  93. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
  94. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
  95. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
  96. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
  97. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
  98. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
  99. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
  100. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
  101. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
  102. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +0 -0
  103. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/css/import.css +0 -0
  104. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/css/openProject.css +0 -0
  105. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/css/quickView.css +0 -0
  106. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/css/tokens.css +0 -0
  107. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/css/viewer.css +0 -0
  108. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/img/apple-touch-icon.png +0 -0
  109. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/img/favicon.ico +0 -0
  110. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/img/logo.ai +0 -0
  111. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/img/logo.svg +0 -0
  112. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/img/logo_with_text.ai +0 -0
  113. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/img/logo_with_text.svg +0 -0
  114. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/main.js +0 -0
  115. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/pluginRegistry.js +0 -0
  116. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/appRouter.js +0 -0
  117. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/appStatus.js +0 -0
  118. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/browsePicker.js +0 -0
  119. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/dataLayer.js +0 -0
  120. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/dataLocation.js +0 -0
  121. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/datasetContext.js +0 -0
  122. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/fileLocation.js +0 -0
  123. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/glRenderer.js +0 -0
  124. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/importFormValidation.js +0 -0
  125. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/keyboardShortcuts.js +0 -0
  126. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/logTerminal.js +0 -0
  127. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/numericData.js +0 -0
  128. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/pageBoot.js +0 -0
  129. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/passVariablesToFrontend.js +0 -0
  130. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/pathPicker.js +0 -0
  131. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/placePicker.js +0 -0
  132. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/resourceRouting.js +0 -0
  133. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/resourceStatus.js +0 -0
  134. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/sessionExpiry.js +0 -0
  135. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/services/simpleEventHandler.js +0 -0
  136. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/vendor.js +0 -0
  137. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/channelList.js +0 -0
  138. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/channelNamesUpload.js +0 -0
  139. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/colorSwatchPicker.js +0 -0
  140. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/columnClassifier.js +0 -0
  141. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/coordinateField.js +0 -0
  142. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/dataSourceField.js +0 -0
  143. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/imageViewer.js +0 -0
  144. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/miniMap.js +0 -0
  145. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/navbarControls.js +0 -0
  146. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/openProjectPage.js +0 -0
  147. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/popoverPortal.js +0 -0
  148. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/projectEdit.js +0 -0
  149. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/quickViewLanding.js +0 -0
  150. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/rainbow.js +0 -0
  151. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/requirementsModal.js +0 -0
  152. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/rgbImageViewer.js +0 -0
  153. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/searchableSelect.js +0 -0
  154. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/segmentationProgress.js +0 -0
  155. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/segmentationWait.js +0 -0
  156. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/toolLoader.js +0 -0
  157. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/viewerControls.js +0 -0
  158. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/viewerManager.js +0 -0
  159. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/views/viewerSidebar.js +0 -0
  160. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/js/workers/tileDecoder.js +0 -0
  161. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/shaders/frag.glsl +0 -0
  162. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/src/shaders/vert.glsl +0 -0
  163. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/_fragment.html +0 -0
  164. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/index.html +0 -0
  165. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/open_project.html +0 -0
  166. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/project_columns.html +0 -0
  167. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/project_edit.html +0 -0
  168. {plexora-0.0.7 → plexora-0.0.8}/plexora/client/templates/upload.html +0 -0
  169. {plexora-0.0.7 → plexora-0.0.8}/plexora/datasource.py +0 -0
  170. {plexora-0.0.7 → plexora-0.0.8}/plexora/jupyter.py +0 -0
  171. {plexora-0.0.7 → plexora-0.0.8}/plexora/nodes.py +0 -0
  172. {plexora-0.0.7 → plexora-0.0.8}/plexora/notebook_env.py +0 -0
  173. {plexora-0.0.7 → plexora-0.0.8}/plexora/paths.py +0 -0
  174. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/__init__.py +0 -0
  175. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/__init__.py +0 -0
  176. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/server/__init__.py +0 -0
  177. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/server/routes.py +0 -0
  178. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/server/state.py +0 -0
  179. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/server/values.py +0 -0
  180. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/server/variables.py +0 -0
  181. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerApi.js +0 -0
  182. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerColors.js +0 -0
  183. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +0 -0
  184. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +0 -0
  185. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerLegend.js +0 -0
  186. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +0 -0
  187. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +0 -0
  188. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cellExplorerState.js +0 -0
  189. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/static/cell_explorer.css +0 -0
  190. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +0 -0
  191. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/__init__.py +0 -0
  192. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/__init__.py +0 -0
  193. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/compose.py +0 -0
  194. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/export.py +0 -0
  195. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/export_jobs.py +0 -0
  196. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/operations.py +0 -0
  197. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/pixels.py +0 -0
  198. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/provenance.py +0 -0
  199. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/render.py +0 -0
  200. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/repository.py +0 -0
  201. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/routes.py +0 -0
  202. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/schema.py +0 -0
  203. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/shapegeom.py +0 -0
  204. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/sources.py +0 -0
  205. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/strokegeom.py +0 -0
  206. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/server/textmetrics.py +0 -0
  207. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureActions.js +0 -0
  208. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureBuilderApi.js +0 -0
  209. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureCanvas.js +0 -0
  210. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureCaptureBoxes.js +0 -0
  211. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureCaptureDock.js +0 -0
  212. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureCaptureTool.js +0 -0
  213. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureChoiceField.js +0 -0
  214. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureColorField.js +0 -0
  215. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureConfirm.js +0 -0
  216. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureContextBar.js +0 -0
  217. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureContextMenu.js +0 -0
  218. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureDocumentState.js +0 -0
  219. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureExportUi.js +0 -0
  220. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureImagePanel.js +0 -0
  221. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureLibrary.js +0 -0
  222. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureLineDefs.js +0 -0
  223. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureLinePanel.js +0 -0
  224. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figurePanelCompositor.js +0 -0
  225. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figurePointEditor.js +0 -0
  226. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureQuickEdit.js +0 -0
  227. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureRichText.js +0 -0
  228. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureSceneSnapshot.js +0 -0
  229. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureSchema.js +0 -0
  230. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureShapeDefs.js +0 -0
  231. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureShapeDrawing.js +0 -0
  232. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureShapeGeometry.js +0 -0
  233. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureShapePanel.js +0 -0
  234. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureSidebarController.js +0 -0
  235. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureStrokeGeometry.js +0 -0
  236. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureTextEditor.js +0 -0
  237. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureTextPanel.js +0 -0
  238. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureViewOptions.js +0 -0
  239. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figureWorkspace.js +0 -0
  240. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/static/figure_builder.css +0 -0
  241. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/templates/figure_builder/library.html +0 -0
  242. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/templates/figure_builder/workspace.html +0 -0
  243. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +0 -0
  244. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/__init__.py +0 -0
  245. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/server/__init__.py +0 -0
  246. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/server/anndata_gates.py +0 -0
  247. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/server/database.py +0 -0
  248. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/server/model.py +0 -0
  249. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/server/routes.py +0 -0
  250. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/server/tableops.py +0 -0
  251. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/static/csvGatingList.js +0 -0
  252. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/static/gating.css +0 -0
  253. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/static/gatingApi.js +0 -0
  254. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/static/gatingSidebarController.js +0 -0
  255. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/templates/gating/legacy.html +0 -0
  256. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/gating/templates/gating/panel.html +0 -0
  257. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/__init__.py +0 -0
  258. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/adapters.py +0 -0
  259. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/geojson.py +0 -0
  260. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/geometry.py +0 -0
  261. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/mapping.py +0 -0
  262. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/operations.py +0 -0
  263. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/repository.py +0 -0
  264. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/routes.py +0 -0
  265. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/schema.py +0 -0
  266. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/server/tableops.py +0 -0
  267. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roi.css +0 -0
  268. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roiApi.js +0 -0
  269. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roiFigureBridge.js +0 -0
  270. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roiGeometry.js +0 -0
  271. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roiRenderer.js +0 -0
  272. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roiSidebarController.js +0 -0
  273. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roiState.js +0 -0
  274. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/static/roiTools.js +0 -0
  275. {plexora-0.0.7 → plexora-0.0.8}/plexora/plugins/roi/templates/roi/panel.html +0 -0
  276. {plexora-0.0.7 → plexora-0.0.8}/plexora/proxy.py +0 -0
  277. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/adapters/__init__.py +0 -0
  278. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/adapters/anndata_adapter.py +0 -0
  279. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/adapters/base.py +0 -0
  280. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/adapters/classify.py +0 -0
  281. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/adapters/csv_adapter.py +0 -0
  282. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/adapters/inspection.py +0 -0
  283. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/adapters/spatialdata_adapter.py +0 -0
  284. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/centroid_tiles.py +0 -0
  285. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/data_migration.py +0 -0
  286. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/database_model.py +0 -0
  287. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/project.py +0 -0
  288. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/models/secret_store.py +0 -0
  289. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/node/app.py +0 -0
  290. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/node/resources.py +0 -0
  291. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/plugins.py +0 -0
  292. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/providers/base.py +0 -0
  293. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/providers/http.py +0 -0
  294. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/providers/local.py +0 -0
  295. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/providers/operations.py +0 -0
  296. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/providers/wire.py +0 -0
  297. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/browse_routes.py +0 -0
  298. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/data_routes.py +0 -0
  299. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/import_routes.py +0 -0
  300. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/page_routes.py +0 -0
  301. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/project_routes.py +0 -0
  302. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/quick_view_routes.py +0 -0
  303. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/system_routes.py +0 -0
  304. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/tool_routes.py +0 -0
  305. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/routes/transfer_routes.py +0 -0
  306. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/addHEColumns.py +0 -0
  307. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/channel_file.py +0 -0
  308. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/dir_listing.py +0 -0
  309. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/fast_png.py +0 -0
  310. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/file_transfer.py +0 -0
  311. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/fullConversion.py +0 -0
  312. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/native_dialog.py +0 -0
  313. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/pre_normalization.py +0 -0
  314. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/segmentation_pyramid.py +0 -0
  315. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/smallestenclosingcircle.py +0 -0
  316. {plexora-0.0.7 → plexora-0.0.8}/plexora/server/utils/tiffsurgeon.py +0 -0
  317. {plexora-0.0.7 → plexora-0.0.8}/plexora/server_cli.py +0 -0
  318. {plexora-0.0.7 → plexora-0.0.8}/plexora/test.py +0 -0
  319. {plexora-0.0.7 → plexora-0.0.8}/plexora.egg-info/dependency_links.txt +0 -0
  320. {plexora-0.0.7 → plexora-0.0.8}/plexora.egg-info/entry_points.txt +0 -0
  321. {plexora-0.0.7 → plexora-0.0.8}/plexora.egg-info/requires.txt +0 -0
  322. {plexora-0.0.7 → plexora-0.0.8}/plexora.egg-info/top_level.txt +0 -0
  323. {plexora-0.0.7 → plexora-0.0.8}/setup.cfg +0 -0
  324. {plexora-0.0.7 → plexora-0.0.8}/tests/test_anndata_adapter.py +0 -0
  325. {plexora-0.0.7 → plexora-0.0.8}/tests/test_api_dataset.py +0 -0
  326. {plexora-0.0.7 → plexora-0.0.8}/tests/test_api_store.py +0 -0
  327. {plexora-0.0.7 → plexora-0.0.8}/tests/test_app_router.py +0 -0
  328. {plexora-0.0.7 → plexora-0.0.8}/tests/test_app_shell.py +0 -0
  329. {plexora-0.0.7 → plexora-0.0.8}/tests/test_auth_token.py +0 -0
  330. {plexora-0.0.7 → plexora-0.0.8}/tests/test_browse_routes.py +0 -0
  331. {plexora-0.0.7 → plexora-0.0.8}/tests/test_cell_color_layer.py +0 -0
  332. {plexora-0.0.7 → plexora-0.0.8}/tests/test_cell_mode_control.py +0 -0
  333. {plexora-0.0.7 → plexora-0.0.8}/tests/test_centroid_tiles.py +0 -0
  334. {plexora-0.0.7 → plexora-0.0.8}/tests/test_channel_names_modal.py +0 -0
  335. {plexora-0.0.7 → plexora-0.0.8}/tests/test_channel_names_upload.py +0 -0
  336. {plexora-0.0.7 → plexora-0.0.8}/tests/test_channel_overview.py +0 -0
  337. {plexora-0.0.7 → plexora-0.0.8}/tests/test_channel_rename_state.py +0 -0
  338. {plexora-0.0.7 → plexora-0.0.8}/tests/test_classify_columns.py +0 -0
  339. {plexora-0.0.7 → plexora-0.0.8}/tests/test_column_classifier_css.py +0 -0
  340. {plexora-0.0.7 → plexora-0.0.8}/tests/test_config_store.py +0 -0
  341. {plexora-0.0.7 → plexora-0.0.8}/tests/test_csv_adapter.py +0 -0
  342. {plexora-0.0.7 → plexora-0.0.8}/tests/test_data_location.py +0 -0
  343. {plexora-0.0.7 → plexora-0.0.8}/tests/test_database_model.py +0 -0
  344. {plexora-0.0.7 → plexora-0.0.8}/tests/test_datalayer_requests.py +0 -0
  345. {plexora-0.0.7 → plexora-0.0.8}/tests/test_derive_anndata_channel_names.py +0 -0
  346. {plexora-0.0.7 → plexora-0.0.8}/tests/test_detect_data_type.py +0 -0
  347. {plexora-0.0.7 → plexora-0.0.8}/tests/test_file_location.py +0 -0
  348. {plexora-0.0.7 → plexora-0.0.8}/tests/test_file_transfer_node.py +0 -0
  349. {plexora-0.0.7 → plexora-0.0.8}/tests/test_hd_slider_domain.py +0 -0
  350. {plexora-0.0.7 → plexora-0.0.8}/tests/test_icon_names.py +0 -0
  351. {plexora-0.0.7 → plexora-0.0.8}/tests/test_jupyter_viewer.py +0 -0
  352. {plexora-0.0.7 → plexora-0.0.8}/tests/test_label_outlines.py +0 -0
  353. {plexora-0.0.7 → plexora-0.0.8}/tests/test_label_tile_lifecycle.py +0 -0
  354. {plexora-0.0.7 → plexora-0.0.8}/tests/test_marker_split.py +0 -0
  355. {plexora-0.0.7 → plexora-0.0.8}/tests/test_mask_without_table.py +0 -0
  356. {plexora-0.0.7 → plexora-0.0.8}/tests/test_metadata_columns.py +0 -0
  357. {plexora-0.0.7 → plexora-0.0.8}/tests/test_mini_map.py +0 -0
  358. {plexora-0.0.7 → plexora-0.0.8}/tests/test_nav_shortcuts.py +0 -0
  359. {plexora-0.0.7 → plexora-0.0.8}/tests/test_node_direct_routing.py +0 -0
  360. {plexora-0.0.7 → plexora-0.0.8}/tests/test_node_disconnect.py +0 -0
  361. {plexora-0.0.7 → plexora-0.0.8}/tests/test_node_dynamic.py +0 -0
  362. {plexora-0.0.7 → plexora-0.0.8}/tests/test_node_image.py +0 -0
  363. {plexora-0.0.7 → plexora-0.0.8}/tests/test_node_routes.py +0 -0
  364. {plexora-0.0.7 → plexora-0.0.8}/tests/test_node_table.py +0 -0
  365. {plexora-0.0.7 → plexora-0.0.8}/tests/test_notebook_env.py +0 -0
  366. {plexora-0.0.7 → plexora-0.0.8}/tests/test_notebook_mode.py +0 -0
  367. {plexora-0.0.7 → plexora-0.0.8}/tests/test_optional_segmentation.py +0 -0
  368. {plexora-0.0.7 → plexora-0.0.8}/tests/test_page_assets.py +0 -0
  369. {plexora-0.0.7 → plexora-0.0.8}/tests/test_page_routes.py +0 -0
  370. {plexora-0.0.7 → plexora-0.0.8}/tests/test_path_picker.py +0 -0
  371. {plexora-0.0.7 → plexora-0.0.8}/tests/test_paths.py +0 -0
  372. {plexora-0.0.7 → plexora-0.0.8}/tests/test_plugin_boundary.py +0 -0
  373. {plexora-0.0.7 → plexora-0.0.8}/tests/test_plugin_css_boundary.py +0 -0
  374. {plexora-0.0.7 → plexora-0.0.8}/tests/test_plugins.py +0 -0
  375. {plexora-0.0.7 → plexora-0.0.8}/tests/test_popover_portal.py +0 -0
  376. {plexora-0.0.7 → plexora-0.0.8}/tests/test_project_edit_routes.py +0 -0
  377. {plexora-0.0.7 → plexora-0.0.8}/tests/test_project_record.py +0 -0
  378. {plexora-0.0.7 → plexora-0.0.8}/tests/test_quick_view_routes.py +0 -0
  379. {plexora-0.0.7 → plexora-0.0.8}/tests/test_reconnect.py +0 -0
  380. {plexora-0.0.7 → plexora-0.0.8}/tests/test_refresh_dataset.py +0 -0
  381. {plexora-0.0.7 → plexora-0.0.8}/tests/test_register_anndata_datasource.py +0 -0
  382. {plexora-0.0.7 → plexora-0.0.8}/tests/test_register_image_datasource.py +0 -0
  383. {plexora-0.0.7 → plexora-0.0.8}/tests/test_remote_globe.py +0 -0
  384. {plexora-0.0.7 → plexora-0.0.8}/tests/test_remote_health.py +0 -0
  385. {plexora-0.0.7 → plexora-0.0.8}/tests/test_remote_state.py +0 -0
  386. {plexora-0.0.7 → plexora-0.0.8}/tests/test_rename_channels.py +0 -0
  387. {plexora-0.0.7 → plexora-0.0.8}/tests/test_requirements_routes.py +0 -0
  388. {plexora-0.0.7 → plexora-0.0.8}/tests/test_resource_status_banner.py +0 -0
  389. {plexora-0.0.7 → plexora-0.0.8}/tests/test_segmentation_mapping.py +0 -0
  390. {plexora-0.0.7 → plexora-0.0.8}/tests/test_segmentation_mode_routes.py +0 -0
  391. {plexora-0.0.7 → plexora-0.0.8}/tests/test_segmentation_pyramid.py +0 -0
  392. {plexora-0.0.7 → plexora-0.0.8}/tests/test_segmentation_wait.py +0 -0
  393. {plexora-0.0.7 → plexora-0.0.8}/tests/test_session_expiry.py +0 -0
  394. {plexora-0.0.7 → plexora-0.0.8}/tests/test_session_walltime.py +0 -0
  395. {plexora-0.0.7 → plexora-0.0.8}/tests/test_settings_page.py +0 -0
  396. {plexora-0.0.7 → plexora-0.0.8}/tests/test_shared_roots.py +0 -0
  397. {plexora-0.0.7 → plexora-0.0.8}/tests/test_spatialdata_adapter.py +0 -0
  398. {plexora-0.0.7 → plexora-0.0.8}/tests/test_tile_url.py +0 -0
  399. {plexora-0.0.7 → plexora-0.0.8}/tests/test_tool_assets.py +0 -0
  400. {plexora-0.0.7 → plexora-0.0.8}/tests/test_tool_cards.py +0 -0
  401. {plexora-0.0.7 → plexora-0.0.8}/tests/test_tool_coexistence.py +0 -0
  402. {plexora-0.0.7 → plexora-0.0.8}/tests/test_tool_switching.py +0 -0
  403. {plexora-0.0.7 → plexora-0.0.8}/tests/test_transfer_routes.py +0 -0
  404. {plexora-0.0.7 → plexora-0.0.8}/tests/test_unreachable_node.py +0 -0
  405. {plexora-0.0.7 → plexora-0.0.8}/tests/test_url_helpers.py +0 -0
  406. {plexora-0.0.7 → plexora-0.0.8}/tests/test_view_menu.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexora
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: OpenSeadragon-based cellular image viewing and analysis tool.
5
5
  License-Expression: LicenseRef-Plexora-Academic-1.0
6
6
  Requires-Python: <3.14,>=3.12
@@ -87,6 +87,31 @@ def _request(url, payload=None, timeout=10):
87
87
  return json.loads(body) if body.strip() else {}
88
88
 
89
89
 
90
+ def asking_process():
91
+ """Which ssh is asking, or None when that cannot be known.
92
+
93
+ One connection to a cluster runs three ssh processes and two of them
94
+ authenticate to the SAME host, so they ask the same question word for word.
95
+ Plexora reuses an answer across hops but must not reuse one that a hop has
96
+ already refused, and those two cases are only distinguishable by which
97
+ process is asking -- identical text otherwise.
98
+
99
+ The POSIX helper `exec`s this interpreter, so this process's parent IS the
100
+ ssh that wants an answer, and it stays the same pid across that ssh's own
101
+ retries. The Windows wrapper is a .bat and cannot exec, so the parent there
102
+ is a transient cmd.exe that would look like a new asker every time --
103
+ exactly the wrong way round. So Windows reports nothing and Plexora falls
104
+ back to treating any repeated question as a refusal, which costs a person
105
+ one extra typing and never replays a rejected secret.
106
+ """
107
+ if os.name == "nt":
108
+ return None
109
+ try:
110
+ return f"pid:{os.getppid()}"
111
+ except OSError:
112
+ return None
113
+
114
+
90
115
  def ask(prompt, env=None):
91
116
  """Post `prompt` back to Plexora and block until somebody answers it.
92
117
 
@@ -107,7 +132,8 @@ def ask(prompt, env=None):
107
132
  timeout = DEFAULT_TIMEOUT
108
133
 
109
134
  opened = _request(_url(base, "prompt", token),
110
- {"nonce": nonce, "prompt": str(prompt)})
135
+ {"nonce": nonce, "prompt": str(prompt),
136
+ "asker": asking_process()})
111
137
  prompt_id = opened.get("id")
112
138
  if not prompt_id:
113
139
  raise AskpassError(opened.get("error") or "Plexora refused the prompt")
@@ -948,6 +948,10 @@ def _build_node_parser():
948
948
  help="Seconds to wait for the node to answer. Raise it when a "
949
949
  "segmentation mask has to be converted first.",
950
950
  )
951
+ node_connect.add_argument(
952
+ "--install", action="store_true",
953
+ help="Install or update Plexora on that host first: `pip install "
954
+ "--upgrade plexora`, in the environment --remote-command names.")
951
955
  node_connect.add_argument("--plugins", default=None,
952
956
  help="Plugins the node should be able to run.")
953
957
 
@@ -1000,6 +1004,7 @@ def _run_node_connect(args):
1000
1004
  remote_port=args.remote_port,
1001
1005
  timeout=args.timeout,
1002
1006
  plugins=args.plugins,
1007
+ install=bool(getattr(args, "install", False)),
1003
1008
  register=register,
1004
1009
  )
1005
1010
 
@@ -1169,6 +1174,15 @@ def _build_connect_parser():
1169
1174
  "where the images are on the cluster and the cell table never "
1170
1175
  "left your laptop. Repeat for each.",
1171
1176
  )
1177
+ connect.add_argument(
1178
+ "--install",
1179
+ action="store_true",
1180
+ help="Install or update Plexora on the remote host before launching "
1181
+ "it: `pip install --upgrade plexora`, in the environment "
1182
+ "--remote-command names. Off by default, because it writes to "
1183
+ "that machine. Saved with --save, and the same switch the "
1184
+ "Settings form calls \"Install or update Plexora\".",
1185
+ )
1172
1186
  connect.add_argument(
1173
1187
  "--no-local-node",
1174
1188
  action="store_true",
@@ -1291,6 +1305,12 @@ def connect_kwargs(args, profile=None):
1291
1305
  or (profile.name if profile is not None else None)),
1292
1306
  "node_port": args.node_port,
1293
1307
  "local_node": not args.no_local_node,
1308
+ # An opt-in either way round, so the flag does not have to beat a saved
1309
+ # value: `--install` on the command line, or a profile that was saved
1310
+ # with it on. Nothing here can turn it off for one run, which is the
1311
+ # right asymmetry -- forgetting to install is a stale Plexora, and
1312
+ # installing by surprise is a write to somebody's account.
1313
+ "install": bool(getattr(args, "install", False) or saved("install", False)),
1294
1314
  }
1295
1315
 
1296
1316
 
@@ -1334,6 +1354,7 @@ def _save_remote(name, target, kwargs):
1334
1354
  serve=tuple(kwargs["also_serve"]),
1335
1355
  local_serve=tuple(kwargs["local_serve"]),
1336
1356
  node_name=kwargs["node_name"],
1357
+ install=kwargs["install"],
1337
1358
  ))
1338
1359
  print(f"Saved as {name!r}. Next time: plexora connect {name}")
1339
1360
 
@@ -3090,6 +3090,88 @@ nav {
3090
3090
  color: var(--text-muted);
3091
3091
  }
3092
3092
 
3093
+ /* A switch shaped as one of the form's own grid cells, so the grid places it
3094
+ beside the box above wherever the dialog is wide enough for two columns.
3095
+ Everything about it that is not layout — the track, the thumb, the travel,
3096
+ the focus ring — is the Settings page's switch at the same sizes, because
3097
+ these are the same control in two places and a user who has seen one should
3098
+ recognise the other. */
3099
+ .connect-switch {
3100
+ flex-direction: row;
3101
+ align-items: flex-start;
3102
+ justify-content: space-between;
3103
+ gap: var(--space-3);
3104
+ margin: 0;
3105
+ cursor: pointer;
3106
+ }
3107
+
3108
+ /* Left alone where the grid has already paired it with the box above -- and
3109
+ widened to the whole row where it would otherwise sit alone in the left
3110
+ column with the toggle stranded mid-dialog. `:last-child:nth-child(odd)` is
3111
+ exactly "the odd one out of a two-column grid"; in the one-column layout a
3112
+ narrow dialog falls back to, every cell spans anyway and this changes
3113
+ nothing. */
3114
+ .connect-form > .connect-switch:last-child:nth-child(odd) {
3115
+ grid-column: 1 / -1;
3116
+ }
3117
+
3118
+ .connect-switch-text {
3119
+ display: flex;
3120
+ flex-direction: column;
3121
+ gap: var(--space-1);
3122
+ min-width: 0;
3123
+ }
3124
+
3125
+ /* Focusable but not visible: the track is the control a mouse sees, and the
3126
+ input is the one the keyboard and the accessibility tree see. */
3127
+ .connect-switch-input {
3128
+ position: absolute;
3129
+ width: 1px;
3130
+ height: 1px;
3131
+ opacity: 0;
3132
+ margin: 0;
3133
+ pointer-events: none;
3134
+ }
3135
+
3136
+ .connect-switch-track {
3137
+ flex: none;
3138
+ position: relative;
3139
+ width: 32px;
3140
+ height: 19px;
3141
+ margin-top: 1px;
3142
+ background: var(--surface-2);
3143
+ border: 1px solid var(--border-subtle);
3144
+ border-radius: 999px;
3145
+ transition: background var(--duration-fast) var(--ease-standard),
3146
+ border-color var(--duration-fast) var(--ease-standard);
3147
+ }
3148
+
3149
+ .connect-switch-thumb {
3150
+ position: absolute;
3151
+ top: 2px;
3152
+ left: 2px;
3153
+ width: 13px;
3154
+ height: 13px;
3155
+ background: var(--text-muted);
3156
+ border-radius: 50%;
3157
+ transition: transform var(--duration-fast) var(--ease-standard),
3158
+ background var(--duration-fast) var(--ease-standard);
3159
+ }
3160
+
3161
+ .connect-switch-input:checked + .connect-switch-track {
3162
+ background: var(--accent-channel);
3163
+ border-color: var(--accent-channel);
3164
+ }
3165
+
3166
+ .connect-switch-input:checked + .connect-switch-track .connect-switch-thumb {
3167
+ background: var(--surface-0);
3168
+ transform: translateX(13px);
3169
+ }
3170
+
3171
+ .connect-switch-input:focus-visible + .connect-switch-track {
3172
+ box-shadow: 0 0 0 3px var(--accent-channel-soft);
3173
+ }
3174
+
3093
3175
  /* Shut on arrival. What is inside is the escape hatch for a preset that is
3094
3176
  almost right — a different partition, a `plexora` ssh cannot find — and
3095
3177
  opening it by default would put a cluster's flag syntax in front of the
@@ -815,6 +815,21 @@
815
815
  margin-bottom: var(--space-4);
816
816
  }
817
817
 
818
+ /* A group title with a control on the end of it. The row keeps the title's own
819
+ bottom margin so the form below sits where it always did -- the switch is a
820
+ passenger on an existing row, not a row of its own. */
821
+ .remote-group-head {
822
+ display: flex;
823
+ align-items: center;
824
+ justify-content: space-between;
825
+ gap: var(--space-4);
826
+ margin-bottom: var(--space-4);
827
+ }
828
+
829
+ .remote-group-head .remote-group-title {
830
+ margin-bottom: 0;
831
+ }
832
+
818
833
  .remote-examples,
819
834
  .remote-ports {
820
835
  font-size: 0.8rem;
@@ -922,6 +937,57 @@
922
937
  box-shadow: 0 0 0 3px var(--accent-channel-soft);
923
938
  }
924
939
 
940
+ /* The same switch, sized to sit on a line of text rather than to own a block.
941
+ Everything it does not restate -- the track's colours, the thumb's travel,
942
+ the focus ring -- is inherited from the rules above, so the two cannot drift
943
+ into looking like two different controls.
944
+
945
+ `flex: none` and `align-items: center` are what make it a passenger: the
946
+ full-width version stretches and top-aligns because it carries a label and a
947
+ sentence of hint, and this one carries three words. */
948
+ .remote-switch-inline {
949
+ flex: none;
950
+ align-items: center;
951
+ justify-content: flex-start;
952
+ gap: var(--space-3);
953
+ padding: 0;
954
+ white-space: nowrap;
955
+ }
956
+
957
+ .remote-switch-inline .remote-switch-label {
958
+ font-size: 0.8rem;
959
+ color: var(--text-secondary);
960
+ }
961
+
962
+ .remote-switch-inline .remote-switch-track {
963
+ width: 32px;
964
+ height: 19px;
965
+ margin-top: 0;
966
+ }
967
+
968
+ .remote-switch-inline .remote-switch-thumb {
969
+ top: 2px;
970
+ left: 2px;
971
+ width: 13px;
972
+ height: 13px;
973
+ }
974
+
975
+ .remote-switch-inline .remote-switch-input:checked
976
+ + .remote-switch-track .remote-switch-thumb {
977
+ transform: translateX(13px);
978
+ }
979
+
980
+ /* Narrow cards put the title and the switch on two lines rather than letting
981
+ either shrink: the title is uppercase and letter-spaced, so it wraps badly,
982
+ and the switch label is the thing being turned on. */
983
+ @media (max-width: 520px) {
984
+ .remote-group-head {
985
+ flex-direction: column;
986
+ align-items: flex-start;
987
+ gap: var(--space-2);
988
+ }
989
+ }
990
+
925
991
  /* -- what a job asks for ------------------------------------------------- */
926
992
 
927
993
  .remote-reveal {
@@ -41,19 +41,38 @@ window.PlexoraConnectionModal = (function () {
41
41
 
42
42
  const Remotes = () => window.PlexoraRemotes;
43
43
 
44
- //: The five things a connection does, in order, with what each is called
45
- //: while it is happening. `queued` marks the step that only exists for a
46
- //: profile whose Plexora runs inside a scheduler job -- drawing it for a
47
- //: plain ssh host would promise a wait that is never coming.
44
+ //: The things a connection does, in order, with what each is called while
45
+ //: it is happening. Two of them are conditional, and each is drawn only
46
+ //: for a profile that actually does it: `queued` for one whose Plexora
47
+ //: runs inside a scheduler job, `install` for one with "install or update
48
+ //: Plexora" switched on. Drawing either unconditionally would promise a
49
+ //: wait that is never coming.
50
+ //:
51
+ //: Installing sits where it does because that is where it runs -- after
52
+ //: the login and before anything is launched, so that what starts
53
+ //: afterwards is the version it just installed.
48
54
  const STEPS = [
49
55
  { state: "connecting", label: "Reaching the machine" },
50
56
  { state: "authenticating", label: "Signing in" },
57
+ { state: "installing", label: "Installing Plexora", install: true },
51
58
  { state: "waiting_for_job", label: "Waiting for the scheduler",
52
59
  queued: true },
53
60
  { state: "tunneling", label: "Opening the tunnel" },
54
61
  { state: "waiting_for_app", label: "Starting Plexora" },
55
62
  ];
56
63
 
64
+ //: What the install step is called when the launch command names an
65
+ //: environment. The name comes from the server -- one reading of one field
66
+ //: (`connect.environment_label`), the same one `pip` will be run through
67
+ //: -- so a step cannot promise an environment the install does not write
68
+ //: to. Without a name it stays the plain label: "in undefined" would be
69
+ //: worse than saying nothing, and a `module load` line genuinely has no
70
+ //: name to give.
71
+ function installLabel(environment) {
72
+ return environment ? "Installing Plexora in " + environment
73
+ : "Installing Plexora";
74
+ }
75
+
57
76
  //: What the last step is called for a data node, which does not start a
58
77
  //: viewer at all. The step list is otherwise identical because the login
59
78
  //: is identical -- same profile, same ssh, same password.
@@ -89,14 +108,23 @@ window.PlexoraConnectionModal = (function () {
89
108
  * was running -- and marking the whole list pending would throw away the
90
109
  * most useful thing on screen, which is how far it got.
91
110
  */
92
- function stepStates(state, kind, queued, lastOpening) {
93
- const steps = STEPS.filter((step) => !step.queued || queued);
111
+ function stepStates(state, kind, queued, lastOpening, install) {
112
+ const steps = STEPS.filter((step) => (!step.queued || queued)
113
+ && (!step.install || Boolean(install)));
94
114
  const failed = state === "failed" || state === "exited";
95
115
  const at = steps.findIndex(
96
116
  (step) => step.state === (failed ? lastOpening : state));
97
117
  return steps.map((step, index) => {
98
- const label = (step.state === "waiting_for_app"
99
- && kind === "node") ? NODE_LAST_STEP : step.label;
118
+ let label = step.label;
119
+ if (step.state === "waiting_for_app" && kind === "node") {
120
+ label = NODE_LAST_STEP;
121
+ } else if (step.state === "installing") {
122
+ // `install` doubles as the environment's name when there is
123
+ // one: the caller has a profile, not two separate answers, and
124
+ // a second parameter that could only ever be set alongside
125
+ // this one is a pair that can be passed inconsistently.
126
+ label = installLabel(typeof install === "string" ? install : "");
127
+ }
100
128
  let status = "pending";
101
129
  if (state === "connected") status = "done";
102
130
  else if (at < 0) status = "pending";
@@ -323,7 +351,14 @@ window.PlexoraConnectionModal = (function () {
323
351
 
324
352
  function paintSteps(state, entry, phase, error) {
325
353
  stepsEl.replaceChildren();
326
- stepStates(state, kind, entry ? entry.queued : false, lastOpening)
354
+ // The environment's name when the profile has one, `true` when it
355
+ // installs without naming one, `false` when it does not install.
356
+ // See stepStates: one value, because "does it install" and "into
357
+ // what" are one fact about one profile.
358
+ const installing = !entry || !entry.install ? false
359
+ : (entry.installEnv || true);
360
+ stepStates(state, kind, entry ? entry.queued : false, lastOpening,
361
+ installing)
327
362
  .forEach((step) => {
328
363
  const item = el("li", "connect-step is-" + step.status);
329
364
  item.append(el("span", "connect-step-mark"));
@@ -627,6 +662,36 @@ window.PlexoraConnectionModal = (function () {
627
662
  return wrap;
628
663
  }
629
664
 
665
+ /**
666
+ * A switch, shaped so the form's own grid places it.
667
+ *
668
+ * `.connect-form` is `repeat(auto-fit, minmax(14rem, 1fr))`, so
669
+ * this is a cell like any other: it sits beside the box above it
670
+ * wherever the dialog is wide enough for two columns, and wraps
671
+ * under it where it is not. That is the whole of "inline when
672
+ * there is room" -- there is no row here to be given one.
673
+ */
674
+ function switchField(key, label, hint, initial) {
675
+ const wrap = el("label", "connect-field connect-switch");
676
+ const text = el("span", "connect-switch-text");
677
+ text.append(el("span", "connect-field-label", label));
678
+ if (hint) text.append(el("span", "connect-field-hint", hint));
679
+ const input = el("input", "connect-switch-input");
680
+ input.type = "checkbox";
681
+ input.checked = Boolean(initial);
682
+ const track = el("span", "connect-switch-track");
683
+ track.setAttribute("aria-hidden", "true");
684
+ track.append(el("span", "connect-switch-thumb"));
685
+ wrap.append(text, input, track);
686
+ // Read by the same loop that reads every text box, so the
687
+ // answers object stays one shape. `checked`, not `value`: the
688
+ // server reads this key as a boolean (recipes.compose keeps
689
+ // the switches out of the trim-to-string pass for exactly
690
+ // this reason).
691
+ boxes[key] = { get value() { return input.checked; } };
692
+ return wrap;
693
+ }
694
+
630
695
  // The three job numbers arrive FILLED IN, not as grey placeholder
631
696
  // text over an empty box. A default nobody can see is a default
632
697
  // nobody can correct, and on a multiplexed image these three are
@@ -686,6 +751,16 @@ window.PlexoraConnectionModal = (function () {
686
751
  "Set this if a login over SSH cannot find plexora — by a wide "
687
752
  + "margin the commonest reason a connection fails. An "
688
753
  + "environment path is enough."));
754
+ // Next to the field that names the environment, because that is
755
+ // the environment it writes to. Off on arrival for every preset:
756
+ // no starting point gets to decide that software should be
757
+ // installed into somebody's account on a machine it has only read
758
+ // the documentation for.
759
+ advancedForm.append(switchField(
760
+ "install", "Install or update Plexora",
761
+ "Runs pip install --upgrade plexora in that environment "
762
+ + "before launching, and shows it in the connection log.",
763
+ false));
689
764
  advanced.append(advancedForm);
690
765
  parts.body.append(advanced);
691
766
 
@@ -698,7 +773,9 @@ window.PlexoraConnectionModal = (function () {
698
773
  button("btn btn-primary", "Save and connect", async () => {
699
774
  const answers = {};
700
775
  Object.keys(boxes).forEach((key) => {
701
- answers[key] = boxes[key].value.trim();
776
+ const value = boxes[key].value;
777
+ answers[key] = typeof value === "string"
778
+ ? value.trim() : value;
702
779
  });
703
780
  try {
704
781
  const saved = await saveRecipe(recipe.id, answers);
@@ -104,6 +104,15 @@ window.PlexoraRemoteGlobe = (function () {
104
104
  return { word: "Healthy", glyph: "circle-check", cls: "is-healthy",
105
105
  ms: probe.ms, detail: "" };
106
106
  }
107
+ //: Its own word, because it is its own situation and the other two
108
+ //: would both be lies. The machine IS answering, so "Not answering" is
109
+ //: false; the server knows exactly what is wrong, so "Unknown" throws
110
+ //: that away. What is stale is the address the open project is using,
111
+ //: and the detail line says so and says what fixes it.
112
+ if (probe.state === "stale") {
113
+ return { word: "Reconnected", glyph: "arrows-rotate", cls: "is-degraded",
114
+ ms: null, detail: probe.detail || "" };
115
+ }
107
116
  return {
108
117
  word: probe.state === "unreachable" ? "Not answering" : "Unknown",
109
118
  glyph: "triangle-exclamation", cls: "is-degraded", ms: null,
@@ -48,8 +48,8 @@ window.PlexoraRemotes = (function () {
48
48
  //: remote_sessions.OPENING_STATES. Anything not here is settled, one way
49
49
  //: or the other, and asking again would be asking a question whose answer
50
50
  //: cannot change on its own.
51
- const OPENING = ["connecting", "authenticating", "waiting_for_job",
52
- "tunneling", "waiting_for_app"];
51
+ const OPENING = ["connecting", "authenticating", "installing",
52
+ "waiting_for_job", "tunneling", "waiting_for_app"];
53
53
 
54
54
  //: What each state is called on screen. One map, because the same word has
55
55
  //: to appear on the card, in the modal's step list and in the globe's
@@ -59,6 +59,7 @@ window.PlexoraRemotes = (function () {
59
59
  idle: "Not connected",
60
60
  connecting: "Connecting",
61
61
  authenticating: "Needs your password",
62
+ installing: "Installing Plexora",
62
63
  waiting_for_job: "Queued",
63
64
  tunneling: "Tunnelling",
64
65
  waiting_for_app: "Starting",
@@ -214,6 +215,16 @@ window.PlexoraRemotes = (function () {
214
215
  //: setting, and worth knowing BEFORE pressing Connect: it
215
216
  //: turns seconds into a queue.
216
217
  queued: remote.srun !== null && remote.srun !== undefined,
218
+ //: Whether connecting to this machine also installs or updates
219
+ //: Plexora on it, and what the environment it writes to is
220
+ //: called. Both from the profile, so the step list can say so
221
+ //: BEFORE it happens rather than after -- which for the one
222
+ //: step that writes to somebody else's machine is the
223
+ //: difference between a plan and a surprise. `installEnv` is a
224
+ //: display name and may be null even when `install` is on: not
225
+ //: every launch command names an environment anything.
226
+ install: Boolean(remote.install),
227
+ installEnv: remote.install_env || null,
217
228
  viewer: viewer,
218
229
  node: node,
219
230
  //: Either half up is "this machine is reachable" for the
@@ -814,8 +814,15 @@
814
814
  card.chip.className = "settings-node-state is-" + state;
815
815
  card.chip.textContent = window.PlexoraRemotes.label(state);
816
816
 
817
+ // On the address line rather than as a badge of its own: these are
818
+ // both "what pressing Connect will do to that machine before you see
819
+ // anything", and the install one is the half that writes.
817
820
  card.address.textContent = entry.detail
818
- + (entry.queued ? " · runs inside a job" : "");
821
+ + (entry.queued ? " · runs inside a job" : "")
822
+ + (entry.install
823
+ ? " · installs Plexora"
824
+ + (entry.installEnv ? " in " + entry.installEnv : "")
825
+ : "");
819
826
 
820
827
  card.phase.textContent = half.phase || "";
821
828
  card.phase.hidden = !half.phase;
@@ -1144,6 +1151,8 @@
1144
1151
  this.revealJob(scheduler);
1145
1152
  const bind = el("settings_remote_bind_node");
1146
1153
  if (bind) bind.checked = !!remote.bind_node;
1154
+ const install = el("settings_remote_install");
1155
+ if (install) install.checked = !!remote.install;
1147
1156
 
1148
1157
  this.forwards = (remote.forwards || []).map(String);
1149
1158
  this.renderForwards();
@@ -1152,7 +1161,12 @@
1152
1161
  // empty disclosure on every Edit makes the form look like it has nine
1153
1162
  // questions when this server answered three of them.
1154
1163
  const advanced = el("settings_remote_advanced");
1164
+ // `install` counts here for a reason the others do not have: it is the
1165
+ // one setting in this form that makes connecting WRITE to the far
1166
+ // machine, so an Edit that hid it would be hiding the answer somebody
1167
+ // is most likely to have come to change.
1155
1168
  if (advanced) advanced.open = scheduler || this.forwards.length > 0
1169
+ || Boolean(remote.install)
1156
1170
  || Boolean(remote.remote_command
1157
1171
  && remote.remote_command !== "plexora");
1158
1172
  text(el("settings_remote_form_title"), "Edit “" + remote.name + "”");
@@ -1166,7 +1180,8 @@
1166
1180
  setValue(REMOTE_FIELDS[key], "");
1167
1181
  });
1168
1182
  setValue("settings_remote_port", "");
1169
- ["settings_remote_use_srun", "settings_remote_bind_node"].forEach((id) => {
1183
+ ["settings_remote_use_srun", "settings_remote_bind_node",
1184
+ "settings_remote_install"].forEach((id) => {
1170
1185
  const box = el(id);
1171
1186
  if (box) box.checked = false;
1172
1187
  });
@@ -1188,6 +1203,7 @@
1188
1203
  });
1189
1204
  body.use_srun = !!(el("settings_remote_use_srun") || {}).checked;
1190
1205
  body.bind_node = !!(el("settings_remote_bind_node") || {}).checked;
1206
+ body.install = !!(el("settings_remote_install") || {}).checked;
1191
1207
  body.forwards = this.forwards.slice();
1192
1208
 
1193
1209
  const button = el("settings_remote_save");
@@ -8,7 +8,7 @@
8
8
  <link rel="apple-touch-icon" href="{{ data.base_url }}/client/src/img/apple-touch-icon.png">
9
9
  <!-- Styles -->
10
10
  <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/tokens.css?v=20260829_view_palette">
11
- <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/main.css?v=20260829_channel_names_switch">
11
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/main.css?v=20260830_install_plexora">
12
12
  {% block style %}{% endblock %}
13
13
 
14
14
 
@@ -87,17 +87,17 @@
87
87
  the one poll that four surfaces used to run three of. It costs nothing
88
88
  on a page that never subscribes -- there is no timer until somebody
89
89
  does. #}
90
- <script src="{{ data.base_url }}/client/src/js/services/remoteState.js?v=20260829_gone_node" defer></script>
90
+ <script src="{{ data.base_url }}/client/src/js/services/remoteState.js?v=20260830_install_plexora" defer></script>
91
91
  {# The connection log, shared by the modal and the Settings page's cards.
92
92
  Before both of them: they call into it as they build. #}
93
93
  <script src="{{ data.base_url }}/client/src/js/services/logTerminal.js?v=20260829_terminal" defer></script>
94
- <script src="{{ data.base_url }}/client/src/js/services/connectionModal.js?v=20260829_add_server3" defer></script>
94
+ <script src="{{ data.base_url }}/client/src/js/services/connectionModal.js?v=20260830_install_plexora" defer></script>
95
95
  <script src="{{ data.base_url }}/client/src/js/services/placePicker.js?v=20260828_connect_modal" defer></script>
96
96
  {# The navbar's connection icon. Deferred like the rest, which still runs
97
97
  it after pageBoot.js below (a normal script beats every deferred one),
98
98
  so PlexoraPage.register exists by the time this file's last line runs.
99
99
  It needs popoverPortal.js, loaded above with the other widgets. #}
100
- <script src="{{ data.base_url }}/client/src/js/services/remoteGlobe.js?v=20260829_job_clock" defer></script>
100
+ <script src="{{ data.base_url }}/client/src/js/services/remoteGlobe.js?v=20260830_node_reconnect" defer></script>
101
101
  {# Says so before a scheduled job ends, from whichever page the user is on.
102
102
  After connectionModal.js, which its one button opens; passive, so it
103
103
  costs nothing while a connection sits settled. #}
@@ -1,6 +1,6 @@
1
1
  {% extends layout|default('base.html', true) %}
2
2
  {% block style %}
3
- <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/settings.css?v=20260829_job_clock">
3
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/settings.css?v=20260830_install_plexora">
4
4
  {% endblock %}
5
5
  {% block content %}
6
6
  <title>Plexora - Settings</title>
@@ -281,7 +281,24 @@
281
281
  <summary class="remote-advanced-summary">Advanced options</summary>
282
282
 
283
283
  <div class="remote-group">
284
- <div class="remote-group-title">Plexora environment</div>
284
+ {# The switch rides the group's own title row
285
+ rather than taking a row of its own: it is the
286
+ same question as the box below it -- which
287
+ environment -- asked as "and keep Plexora
288
+ current in it". A dedicated row would put an
289
+ uppercase section label and a lone toggle on two
290
+ lines that both say "environment". #}
291
+ <div class="remote-group-head">
292
+ <div class="remote-group-title">Plexora environment</div>
293
+ <label class="remote-switch remote-switch-inline">
294
+ <span class="remote-switch-label">Install or update Plexora</span>
295
+ <input type="checkbox" id="settings_remote_install"
296
+ class="remote-switch-input">
297
+ <span class="remote-switch-track" aria-hidden="true">
298
+ <span class="remote-switch-thumb"></span>
299
+ </span>
300
+ </label>
301
+ </div>
285
302
  <div class="remote-form">
286
303
  <label class="remote-field">
287
304
  <span class="remote-label">Plexora command or environment</span>
@@ -290,7 +307,12 @@
290
307
  placeholder="plexora" autocomplete="off">
291
308
  <span class="remote-hint">
292
309
  If SSH cannot find Plexora, specify its
293
- environment or launch command.
310
+ environment or launch command. With
311
+ <strong>Install or update Plexora</strong>
312
+ on, connecting runs
313
+ <code>pip install --upgrade plexora</code>
314
+ in this same environment first, and shows
315
+ the whole of it in the connection log.
294
316
  </span>
295
317
  </label>
296
318
  {# The examples are behind a disclosure because
@@ -541,5 +563,5 @@
541
563
  {# remoteState.js is loaded by base.html, above and deferred, so it is parsed
542
564
  before this file runs -- the Remote servers section is one of its
543
565
  subscribers rather than a poller of its own. #}
544
- <script src="{{ data.base_url }}/client/src/js/views/settingsPage.js?v=20260829_job_clock" defer></script>
566
+ <script src="{{ data.base_url }}/client/src/js/views/settingsPage.js?v=20260830_install_plexora" defer></script>
545
567
  {% endblock %}