plexora 0.0.9__tar.gz → 0.0.10__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.
- {plexora-0.0.9/plexora.egg-info → plexora-0.0.10}/PKG-INFO +1 -1
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/main.js +141 -8
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/remoteGlobe.js +58 -2
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/remoteState.js +49 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/resourceRouting.js +47 -4
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/base.html +3 -3
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/index.html +1 -1
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/remote_sessions.py +65 -7
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/settings_routes.py +24 -3
- {plexora-0.0.9 → plexora-0.0.10/plexora.egg-info}/PKG-INFO +1 -1
- {plexora-0.0.9 → plexora-0.0.10}/pyproject.toml +1 -1
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_remote_connect.py +155 -0
- {plexora-0.0.9 → plexora-0.0.10}/LICENSE +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/MANIFEST.in +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/README.md +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/__main__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/_resources.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/_url.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/api/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/api/dataset.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/api/http.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/api/plugin.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/api/store.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/askpass.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/cli.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/354_bundle.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/354_bundle.js.LICENSE.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/418_bundle.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/418_bundle.js.LICENSE.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/770_bundle.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/vendor_bundle.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/dist/vendor_bundle.js.LICENSE.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/css/import.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/css/main.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/css/openProject.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/css/quickView.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/css/settings.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/css/tokens.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/css/viewer.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/img/apple-touch-icon.png +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/img/favicon.ico +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/img/logo.ai +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/img/logo.svg +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/img/logo_with_text.ai +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/img/logo_with_text.svg +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/pluginRegistry.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/appRouter.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/appStatus.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/browsePicker.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/connectionModal.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/dataLayer.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/dataLocation.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/datasetContext.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/fileLocation.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/glRenderer.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/importFormValidation.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/keyboardShortcuts.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/logTerminal.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/numericData.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/pageBoot.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/passVariablesToFrontend.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/pathPicker.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/placePicker.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/resourceStatus.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/sessionExpiry.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/services/simpleEventHandler.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/vendor.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/channelList.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/channelNamesUpload.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/colorSwatchPicker.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/columnClassifier.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/coordinateField.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/dataSourceField.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/imageViewer.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/miniMap.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/navbarControls.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/openProjectPage.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/popoverPortal.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/projectEdit.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/quickViewLanding.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/rainbow.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/requirementsModal.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/rgbImageViewer.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/searchableSelect.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/segmentationProgress.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/segmentationWait.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/settingsPage.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/toolLoader.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/viewerControls.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/viewerManager.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/views/viewerSidebar.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/js/workers/tileDecoder.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/shaders/frag.glsl +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/src/shaders/vert.glsl +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/_fragment.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/open_project.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/project_columns.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/project_edit.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/settings.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/client/templates/upload.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/connect.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/datasource.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/jupyter.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/nodes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/notebook_env.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/paths.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/server/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/server/routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/server/state.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/server/values.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/server/variables.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerApi.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerColors.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerLegend.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cellExplorerState.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/static/cell_explorer.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/compose.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/export.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/export_jobs.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/operations.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/pixels.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/provenance.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/render.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/repository.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/schema.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/shapegeom.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/sources.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/strokegeom.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/server/textmetrics.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureActions.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureBuilderApi.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureCanvas.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureCaptureBoxes.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureCaptureDock.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureCaptureTool.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureChoiceField.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureColorField.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureConfirm.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureContextBar.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureContextMenu.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureDocumentState.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureExportUi.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureImagePanel.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureLibrary.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureLineDefs.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureLinePanel.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figurePanelCompositor.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figurePointEditor.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureQuickEdit.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureRichText.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureSceneSnapshot.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureSchema.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureShapeDefs.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureShapeDrawing.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureShapeGeometry.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureShapePanel.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureSidebarController.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureStrokeGeometry.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureTextEditor.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureTextPanel.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureViewOptions.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figureWorkspace.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/static/figure_builder.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/templates/figure_builder/library.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/templates/figure_builder/workspace.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/server/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/server/anndata_gates.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/server/database.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/server/model.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/server/routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/server/tableops.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/static/csvGatingList.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/static/gating.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/static/gatingApi.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/static/gatingSidebarController.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/templates/gating/legacy.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/gating/templates/gating/panel.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/adapters.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/geojson.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/geometry.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/mapping.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/operations.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/repository.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/schema.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/server/tableops.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roi.css +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roiApi.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roiFigureBridge.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roiGeometry.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roiRenderer.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roiSidebarController.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roiState.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/static/roiTools.js +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/plugins/roi/templates/roi/panel.html +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/proxy.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/adapters/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/adapters/anndata_adapter.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/adapters/base.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/adapters/classify.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/adapters/csv_adapter.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/adapters/inspection.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/adapters/spatialdata_adapter.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/centroid_tiles.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/data_migration.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/data_model.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/database_model.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/nodes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/project.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/recipes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/remotes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/models/secret_store.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/node/api.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/node/app.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/node/resources.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/plugins.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/providers/__init__.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/providers/base.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/providers/http.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/providers/local.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/providers/node.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/providers/operations.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/providers/wire.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/browse_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/data_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/import_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/page_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/project_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/quick_view_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/system_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/tool_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/routes/transfer_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/addHEColumns.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/channel_file.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/dir_listing.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/fast_png.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/file_transfer.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/fullConversion.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/native_dialog.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/pre_normalization.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/segmentation_pyramid.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/smallestenclosingcircle.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server/utils/tiffsurgeon.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/server_cli.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora/test.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora.egg-info/SOURCES.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora.egg-info/dependency_links.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora.egg-info/entry_points.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora.egg-info/requires.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/plexora.egg-info/top_level.txt +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/setup.cfg +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_anndata_adapter.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_api_dataset.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_api_store.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_app_router.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_app_shell.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_auth_token.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_browse_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_cell_color_layer.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_cell_mode_control.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_centroid_tiles.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_channel_names_modal.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_channel_names_upload.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_channel_overview.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_channel_rename_state.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_classify_columns.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_cli.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_column_classifier_css.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_config_store.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_connect.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_connection_modal.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_csv_adapter.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_data_location.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_database_model.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_datalayer_requests.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_derive_anndata_channel_names.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_detect_data_type.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_file_location.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_file_transfer_node.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_hd_slider_domain.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_icon_names.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_jupyter_viewer.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_label_outlines.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_label_tile_lifecycle.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_marker_split.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_mask_without_table.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_metadata_columns.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_mini_map.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_nav_shortcuts.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_direct_routing.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_disconnect.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_dynamic.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_image.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_reconnect.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_table.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_node_warm_and_cache.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_notebook_env.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_notebook_mode.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_optional_segmentation.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_page_assets.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_page_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_path_picker.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_paths.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_plugin_boundary.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_plugin_css_boundary.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_plugins.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_popover_portal.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_project_edit_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_project_record.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_quick_view_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_recipes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_reconnect.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_refresh_dataset.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_register_anndata_datasource.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_register_image_datasource.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_remote_globe.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_remote_health.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_remote_state.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_rename_channels.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_requirements_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_resource_status_banner.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_segmentation_mapping.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_segmentation_mode_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_segmentation_pyramid.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_segmentation_wait.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_session_expiry.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_session_walltime.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_settings_page.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_settings_remotes_page.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_shared_roots.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_spatialdata_adapter.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_tile_url.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_tool_assets.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_tool_cards.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_tool_coexistence.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_tool_switching.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_transfer_routes.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_unreachable_node.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_url_helpers.py +0 -0
- {plexora-0.0.9 → plexora-0.0.10}/tests/test_view_menu.py +0 -0
|
@@ -60,10 +60,9 @@ async function init(config) {
|
|
|
60
60
|
// to "everything is on this server" without a single probe, and the rewrite
|
|
61
61
|
// below is exactly what it has always been. For a project reading from a
|
|
62
62
|
// data node it decides, once per tab, whether this browser can reach that
|
|
63
|
-
// node directly -- see services/resourceRouting.js.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const segRoute = PlexoraRouting.tileSource(routing, "segmentation");
|
|
63
|
+
// node directly -- see services/resourceRouting.js. `let`, because a
|
|
64
|
+
// reconnect mid-session replaces it -- see repairRouting below.
|
|
65
|
+
let routing = await PlexoraRouting.load(datasource);
|
|
67
66
|
|
|
68
67
|
// A layer that could not be loaded at all is already absent from `config`
|
|
69
68
|
// by this point, so nothing below would ever mention it. Not awaited: the
|
|
@@ -74,21 +73,49 @@ async function init(config) {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
if (Array.isArray(config.imageData)) {
|
|
77
|
-
config.imageData.forEach(function (channel
|
|
76
|
+
config.imageData.forEach(function (channel) {
|
|
78
77
|
if (channel.src && channel.src.startsWith("/")) {
|
|
79
78
|
channel.src = plexoraUrl(channel.src);
|
|
80
79
|
}
|
|
80
|
+
// The address THIS server serves the layer at, kept apart from the
|
|
81
|
+
// routed rewrite below so routing can be applied again later. A
|
|
82
|
+
// reconnect changes a node's port and token, and re-deriving the
|
|
83
|
+
// direct address from the previous direct address would strand the
|
|
84
|
+
// one case that has to work: a route that has fallen back to the
|
|
85
|
+
// proxy and later becomes direct again.
|
|
86
|
+
channel.origSrc = channel.src;
|
|
87
|
+
});
|
|
88
|
+
applyRouting(routing);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Point every layer's tile address at what `routing` says, from scratch.
|
|
93
|
+
*
|
|
94
|
+
* Idempotent over `origSrc`, so it can run again when the answer changes
|
|
95
|
+
* -- at boot, and on every repair after a node reconnects or goes away.
|
|
96
|
+
*/
|
|
97
|
+
function applyRouting(resolved) {
|
|
98
|
+
const imageRoute = PlexoraRouting.tileSource(resolved, "image");
|
|
99
|
+
const segRoute = PlexoraRouting.tileSource(resolved, "segmentation");
|
|
100
|
+
(config.imageData || []).forEach(function (channel, index) {
|
|
101
|
+
if (!channel.origSrc) return;
|
|
81
102
|
// imageData[0] is the label layer when, and only when, the project
|
|
82
103
|
// has a mask -- the same rule ViewerManager.raiseLabelLayer applies
|
|
83
104
|
// and for the same reason, so the two cannot disagree about which
|
|
84
105
|
// entry is which.
|
|
85
106
|
const isLabel = index === 0 && Boolean(config.segmentation);
|
|
86
107
|
const route = isLabel ? segRoute : imageRoute;
|
|
87
|
-
if (!route)
|
|
108
|
+
if (!route) {
|
|
109
|
+
// This server's own address: the proxy path, which is also the
|
|
110
|
+
// only path for a local layer.
|
|
111
|
+
channel.src = channel.origSrc;
|
|
112
|
+
channel.srcQuery = "";
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
88
115
|
// An image names which channel in the path; a mask has one plane
|
|
89
116
|
// and names nothing. The key is the last segment of the address
|
|
90
|
-
// this
|
|
91
|
-
const key = channel.
|
|
117
|
+
// this server would serve the entry at.
|
|
118
|
+
const key = channel.origSrc.replace(/\/+$/, "").split("/").pop();
|
|
92
119
|
channel.src = route.appendKey ? route.base + key + "/" : route.base;
|
|
93
120
|
// Carried apart from `src` rather than appended to it, because
|
|
94
121
|
// `channel_add` reads the key back out of `src` by position and a
|
|
@@ -163,6 +190,112 @@ async function init(config) {
|
|
|
163
190
|
__plexora.dataset = PlexoraDataset.build(config, imageChannels, dd);
|
|
164
191
|
channelList.init(dd);
|
|
165
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Rebuild every tiled image off the addresses `config` now holds.
|
|
195
|
+
*
|
|
196
|
+
* The same remove-and-re-add the HD toggle uses, and for the same reason
|
|
197
|
+
* (see ViewerManager.setHdMode): invalidating tiles in place leaves stale
|
|
198
|
+
* canvases on screen. `currentChannels` still holds each layer's OLD url,
|
|
199
|
+
* which is exactly what channel_remove matches world items against -- so
|
|
200
|
+
* the order is fixed: applyRouting first, this second.
|
|
201
|
+
*/
|
|
202
|
+
function rebuildTileLayers() {
|
|
203
|
+
const world = seaDragonViewer.viewer && seaDragonViewer.viewer.world;
|
|
204
|
+
if (!world) return;
|
|
205
|
+
Object.keys(channelList.currentChannels).map(Number).forEach((srcIdx) => {
|
|
206
|
+
viewerManager.channel_remove(srcIdx);
|
|
207
|
+
viewerManager.channel_add(srcIdx);
|
|
208
|
+
});
|
|
209
|
+
if (config.segmentation) {
|
|
210
|
+
for (let i = world.getItemCount() - 1; i >= 0; i -= 1) {
|
|
211
|
+
const item = world.getItemAt(i);
|
|
212
|
+
if (item && item.source && item.source.tileFormat === 32) {
|
|
213
|
+
world.removeItem(item);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Both guards exist to make lazy loading happen once; this is the
|
|
217
|
+
// one caller that means "again" -- the same resets
|
|
218
|
+
// adoptSegmentation makes when a mask arrives mid-session.
|
|
219
|
+
// `noLabel` is set by the error callback when the label layer
|
|
220
|
+
// failed to load, which during an outage it did.
|
|
221
|
+
seaDragonViewer.noLabel = false;
|
|
222
|
+
viewerManager.labelLayerRequested = false;
|
|
223
|
+
viewerManager.load_label_image();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Say again what is missing, now that the answer may have changed.
|
|
229
|
+
*
|
|
230
|
+
* One banner at a time: whatever strip is up is about the addresses that
|
|
231
|
+
* were just replaced, and report() draws a fresh one when there is still
|
|
232
|
+
* something to say (and clears its own per-tab memories when there is not).
|
|
233
|
+
*/
|
|
234
|
+
function rereportResources(resolved) {
|
|
235
|
+
if (!window.PlexoraResourceStatus) return;
|
|
236
|
+
document.querySelectorAll(".resource-status-banner")
|
|
237
|
+
.forEach((strip) => strip.remove());
|
|
238
|
+
PlexoraResourceStatus.report(datasource, resolved);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//: The in-flight repair, shared: a burst of failing tiles and a reconnect
|
|
242
|
+
//: event landing together is one re-resolution, not several racing ones.
|
|
243
|
+
let repairing = null;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Re-resolve where tiles come from, and take the answer on in place.
|
|
247
|
+
*
|
|
248
|
+
* What a reconnect used to require a page reload for: the node comes back
|
|
249
|
+
* on a new port with a new token, the server's own providers re-resolve on
|
|
250
|
+
* their next call (nodes.address_generation), and this is the browser-side
|
|
251
|
+
* counterpart -- without it every direct tile URL on the page kept the
|
|
252
|
+
* dead address, which made each reconnect look like it had done nothing.
|
|
253
|
+
*/
|
|
254
|
+
function repairRouting() {
|
|
255
|
+
if (!window.PlexoraRouting) return Promise.resolve(null);
|
|
256
|
+
if (repairing) return repairing;
|
|
257
|
+
repairing = (async () => {
|
|
258
|
+
const before = JSON.stringify(routing && routing.routes || {});
|
|
259
|
+
const fresh = await PlexoraRouting.refresh(datasource);
|
|
260
|
+
routing = fresh;
|
|
261
|
+
rereportResources(fresh);
|
|
262
|
+
if (JSON.stringify(fresh.routes || {}) === before) return null;
|
|
263
|
+
applyRouting(fresh);
|
|
264
|
+
rebuildTileLayers();
|
|
265
|
+
return fresh;
|
|
266
|
+
})().finally(() => { repairing = null; });
|
|
267
|
+
return repairing;
|
|
268
|
+
}
|
|
269
|
+
__plexora.repairRouting = repairRouting;
|
|
270
|
+
|
|
271
|
+
// A data node connecting or going away is the moment tile addresses can
|
|
272
|
+
// change. remoteState announces the transition (it is already watching
|
|
273
|
+
// whenever one can happen in this tab); repairing on it is what lets a
|
|
274
|
+
// reconnect heal an open viewer instead of needing a reload nobody was
|
|
275
|
+
// told about.
|
|
276
|
+
window.addEventListener("plexora:remote-nodes-changed", () => {
|
|
277
|
+
repairRouting();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
//: How often failing tiles may trigger a repair attempt. A dead node fails
|
|
281
|
+
//: tiles for as long as it is dead; asking the server once per window is
|
|
282
|
+
//: what keeps that from becoming a poll.
|
|
283
|
+
const TILE_FAILURE_REPAIR_MS = 30000;
|
|
284
|
+
let lastTileRepair = 0;
|
|
285
|
+
// A burst of failing tiles is how a moved or dead node actually presents
|
|
286
|
+
// mid-session -- nothing else on the page is watching when no dialog is
|
|
287
|
+
// open. Repairing answers both cases: an address that changed is taken on
|
|
288
|
+
// in place, and a node that is gone from the map gets the resource-status
|
|
289
|
+
// report (the modal with the Connect button) instead of silent timeouts.
|
|
290
|
+
if (seaDragonViewer.viewer && seaDragonViewer.viewer.addHandler) {
|
|
291
|
+
seaDragonViewer.viewer.addHandler("tile-load-failed", () => {
|
|
292
|
+
const now = Date.now();
|
|
293
|
+
if (now - lastTileRepair < TILE_FAILURE_REPAIR_MS) return;
|
|
294
|
+
lastTileRepair = now;
|
|
295
|
+
repairRouting();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
166
299
|
/**
|
|
167
300
|
* Re-fetch what the server says this project's numbers are.
|
|
168
301
|
*
|
|
@@ -85,7 +85,7 @@ window.PlexoraRemoteGlobe = (function () {
|
|
|
85
85
|
* Collapsing them loses the case that matters -- a session that still
|
|
86
86
|
* reads connected against a node that has stopped answering.
|
|
87
87
|
*/
|
|
88
|
-
function healthOf(entry, health) {
|
|
88
|
+
function healthOf(entry, health, stale) {
|
|
89
89
|
//: The probe first, and the session only as a fallback. A data node
|
|
90
90
|
//: outlives the process that started it, so after a restart there is
|
|
91
91
|
//: no session and the registry still holds the node -- asking the
|
|
@@ -101,6 +101,18 @@ window.PlexoraRemoteGlobe = (function () {
|
|
|
101
101
|
cls: "is-checking", ms: null, detail: "" };
|
|
102
102
|
}
|
|
103
103
|
if (probe.state === "healthy") {
|
|
104
|
+
//: The BROWSER's own version of the server's "stale" below, and it
|
|
105
|
+
//: outlasts it: the server's held address heals itself on its next
|
|
106
|
+
//: proxied call, while the page's direct tile URLs stay wherever
|
|
107
|
+
//: the tiles on screen were fetched from. Checked here, on top of
|
|
108
|
+
//: a healthy probe, because both claims are true at once -- the
|
|
109
|
+
//: machine IS well, and this page is not reading from it.
|
|
110
|
+
if (stale && stale[nodeNameOf(entry)]) {
|
|
111
|
+
return { word: "Reconnected", glyph: "arrows-rotate",
|
|
112
|
+
cls: "is-degraded", ms: null,
|
|
113
|
+
detail: "That machine is answering at a new address; "
|
|
114
|
+
+ "this page is being repointed at it now." };
|
|
115
|
+
}
|
|
104
116
|
return { word: "Healthy", glyph: "circle-check", cls: "is-healthy",
|
|
105
117
|
ms: probe.ms, detail: "" };
|
|
106
118
|
}
|
|
@@ -120,6 +132,33 @@ window.PlexoraRemoteGlobe = (function () {
|
|
|
120
132
|
};
|
|
121
133
|
}
|
|
122
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Which nodes the open page is still addressing at a retired location.
|
|
137
|
+
*
|
|
138
|
+
* Compares what this page's tiles were actually built from
|
|
139
|
+
* (`PlexoraRouting.held`) with what `/resource_routing` answers NOW. Only
|
|
140
|
+
* a direct route can be stale in the browser -- a proxied layer goes
|
|
141
|
+
* through this server, whose providers re-resolve on their own -- and a
|
|
142
|
+
* node that has left the map entirely is not stale, it is gone, which the
|
|
143
|
+
* probe already says in its own words. Empty on every page that never
|
|
144
|
+
* resolved routing, which is every page but the viewer.
|
|
145
|
+
*/
|
|
146
|
+
function staleNodes(datasource, candidates) {
|
|
147
|
+
const held = window.PlexoraRouting && PlexoraRouting.held
|
|
148
|
+
? PlexoraRouting.held(datasource) : null;
|
|
149
|
+
const routes = (held && held.routes) || {};
|
|
150
|
+
const stale = {};
|
|
151
|
+
Object.keys(routes).forEach((kind) => {
|
|
152
|
+
const mine = routes[kind];
|
|
153
|
+
const now = candidates[kind];
|
|
154
|
+
if (!mine || mine.mode !== "direct" || !now) return;
|
|
155
|
+
if (mine.base !== now.tile_base || mine.query !== now.query) {
|
|
156
|
+
stale[mine.node] = true;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
return stale;
|
|
160
|
+
}
|
|
161
|
+
|
|
123
162
|
//: The one mount there will ever be. This icon lives in the NAVBAR, which
|
|
124
163
|
//: the router does not swap -- only the page below it is replaced -- so
|
|
125
164
|
//: `PlexoraPage.register` runs again on every internal navigation against
|
|
@@ -158,6 +197,10 @@ window.PlexoraRemoteGlobe = (function () {
|
|
|
158
197
|
//: and a different one from "we have not asked".
|
|
159
198
|
let imageNode = null;
|
|
160
199
|
let routingRead = false;
|
|
200
|
+
//: Node names whose registry address no longer matches what this
|
|
201
|
+
//: page's tiles were built from -- see staleNodes. Computed with the
|
|
202
|
+
//: routing read, drawn over a healthy probe as "Reconnected".
|
|
203
|
+
let pageStale = {};
|
|
161
204
|
//: What `/remote_health` last said, by profile name, and which set of
|
|
162
205
|
//: connected machines that answer was about -- so becoming connected
|
|
163
206
|
//: while the panel is open re-asks, and a poll that changed nothing
|
|
@@ -234,6 +277,7 @@ window.PlexoraRemoteGlobe = (function () {
|
|
|
234
277
|
|
|
235
278
|
imageNode = null;
|
|
236
279
|
routingRead = false;
|
|
280
|
+
pageStale = {};
|
|
237
281
|
health = {};
|
|
238
282
|
healthFor = null;
|
|
239
283
|
readRouting();
|
|
@@ -295,6 +339,18 @@ window.PlexoraRemoteGlobe = (function () {
|
|
|
295
339
|
const routes = (payload && payload.routes) || {};
|
|
296
340
|
imageNode = (routes.image && routes.image.node) || null;
|
|
297
341
|
routingRead = true;
|
|
342
|
+
pageStale = staleNodes(datasource, routes);
|
|
343
|
+
if (Object.keys(pageStale).length) {
|
|
344
|
+
// Saying so is this panel's job; fixing it is the
|
|
345
|
+
// viewer's. The same event a watched reconnect fires,
|
|
346
|
+
// so there is one repair path -- this is the backstop
|
|
347
|
+
// for a reconnect made from another tab, which no poll
|
|
348
|
+
// in this one was awake to see.
|
|
349
|
+
window.dispatchEvent(new CustomEvent(
|
|
350
|
+
"plexora:remote-nodes-changed",
|
|
351
|
+
{ detail: { changed: Object.keys(pageStale).map(
|
|
352
|
+
(node) => ({ name: null, node: node, up: true })) } }));
|
|
353
|
+
}
|
|
298
354
|
if (panel) draw(Remotes().snapshot());
|
|
299
355
|
})
|
|
300
356
|
.catch(() => {});
|
|
@@ -430,7 +486,7 @@ window.PlexoraRemoteGlobe = (function () {
|
|
|
430
486
|
item.append(top);
|
|
431
487
|
|
|
432
488
|
const bottom = el("div", "remote-conn-bottom");
|
|
433
|
-
const state = healthOf(entry, health);
|
|
489
|
+
const state = healthOf(entry, health, pageStale);
|
|
434
490
|
const well = el("span", "remote-conn-health " + state.cls);
|
|
435
491
|
well.append(icon(state.glyph));
|
|
436
492
|
well.append(el("span", null, state.word));
|
|
@@ -299,7 +299,52 @@ window.PlexoraRemotes = (function () {
|
|
|
299
299
|
timer = window.setTimeout(() => { refresh(); }, POLL_MS);
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Which profiles' data nodes changed condition between two snapshots.
|
|
304
|
+
*
|
|
305
|
+
* The one thing a snapshot difference can say that no subscriber can:
|
|
306
|
+
* a node session coming up or going away changes which ADDRESS the open
|
|
307
|
+
* project's tiles should be fetched from, and the page holding those tile
|
|
308
|
+
* URLs (main.js) is not a subscriber -- it resolved routing once at boot.
|
|
309
|
+
* The event is what lets it repair itself instead of failing against a
|
|
310
|
+
* port that has gone until somebody thinks to reload.
|
|
311
|
+
*
|
|
312
|
+
* Only real transitions: a profile present in both snapshots whose node
|
|
313
|
+
* half changed being-up, map name, or registry name. The first snapshot
|
|
314
|
+
* has nothing to differ from, and a failed poll republishes the same
|
|
315
|
+
* entries object, so neither fires anything.
|
|
316
|
+
*/
|
|
317
|
+
function nodeChanges(previous, next) {
|
|
318
|
+
if (!previous.loaded || !next.loaded) return [];
|
|
319
|
+
if (previous.entries === next.entries) return [];
|
|
320
|
+
const before = new Map();
|
|
321
|
+
(previous.entries || []).forEach((entry) => {
|
|
322
|
+
before.set(entry.name, entry.node);
|
|
323
|
+
});
|
|
324
|
+
const changed = [];
|
|
325
|
+
(next.entries || []).forEach((entry) => {
|
|
326
|
+
const was = before.get(entry.name);
|
|
327
|
+
if (!was) return;
|
|
328
|
+
const wasUp = was.state === "connected" || Boolean(was.node);
|
|
329
|
+
const isUp = entry.node.state === "connected"
|
|
330
|
+
|| Boolean(entry.node.node);
|
|
331
|
+
if (wasUp !== isUp
|
|
332
|
+
|| (was.node || null) !== (entry.node.node || null)
|
|
333
|
+
|| (was.registered || null)
|
|
334
|
+
!== (entry.node.registered || null)) {
|
|
335
|
+
changed.push({
|
|
336
|
+
name: entry.name,
|
|
337
|
+
node: entry.node.node || entry.node.registered
|
|
338
|
+
|| was.node || was.registered || null,
|
|
339
|
+
up: isUp,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
return changed;
|
|
344
|
+
}
|
|
345
|
+
|
|
302
346
|
function publish(next) {
|
|
347
|
+
const changed = nodeChanges(current, next);
|
|
303
348
|
current = next;
|
|
304
349
|
subscribers.forEach((sub) => {
|
|
305
350
|
try {
|
|
@@ -311,6 +356,10 @@ window.PlexoraRemotes = (function () {
|
|
|
311
356
|
if (window.console) console.error("remote state subscriber", e);
|
|
312
357
|
}
|
|
313
358
|
});
|
|
359
|
+
if (changed.length) {
|
|
360
|
+
window.dispatchEvent(new CustomEvent("plexora:remote-nodes-changed",
|
|
361
|
+
{ detail: { changed } }));
|
|
362
|
+
}
|
|
314
363
|
}
|
|
315
364
|
|
|
316
365
|
/**
|
|
@@ -48,6 +48,14 @@ window.PlexoraRouting = (function () {
|
|
|
48
48
|
//: wait on the same probe rather than starting their own.
|
|
49
49
|
const pending = new Map();
|
|
50
50
|
|
|
51
|
+
//: datasource -> the last fully-resolved table this page handed out: what
|
|
52
|
+
//: the page is ACTUALLY using, kept for anything that compares it against
|
|
53
|
+
//: the registry's current answer (the globe's stale check). Overwritten
|
|
54
|
+
//: when a re-resolution lands, never cleared by `forget` -- while a fresh
|
|
55
|
+
//: probe is in flight the old table is still the truthful account of what
|
|
56
|
+
//: the tiles on screen were built from.
|
|
57
|
+
const settled = new Map();
|
|
58
|
+
|
|
51
59
|
//: The answer for a project with nothing on a node. Frozen and shared:
|
|
52
60
|
//: every lookup against it returns "local", which is what every call site
|
|
53
61
|
//: already assumed.
|
|
@@ -119,7 +127,11 @@ window.PlexoraRouting = (function () {
|
|
|
119
127
|
+ "?datasource=" + encodeURIComponent(datasource))
|
|
120
128
|
.then((response) => (response.ok ? response.json() : { routes: {} }))
|
|
121
129
|
.then((body) => decide(datasource, body.routes || {}))
|
|
122
|
-
.catch(() => NOTHING_REMOTE)
|
|
130
|
+
.catch(() => NOTHING_REMOTE)
|
|
131
|
+
.then((resolved) => {
|
|
132
|
+
settled.set(datasource, resolved);
|
|
133
|
+
return resolved;
|
|
134
|
+
});
|
|
123
135
|
|
|
124
136
|
pending.set(datasource, promise);
|
|
125
137
|
return promise;
|
|
@@ -142,8 +154,14 @@ window.PlexoraRouting = (function () {
|
|
|
142
154
|
});
|
|
143
155
|
|
|
144
156
|
const asked = Array.from(byNode.entries()).map(([node, route]) => {
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
// Only a remembered YES is reused. A remembered "unreachable" was
|
|
158
|
+
// true of a moment -- a node mid-restart, a tunnel not yet up --
|
|
159
|
+
// and reusing it pinned the whole tab to the proxy hop for as long
|
|
160
|
+
// as it stayed open, silently, after the node came back. Probing
|
|
161
|
+
// again costs this one load at most PROBE_TIMEOUT_MS; being wrong
|
|
162
|
+
// the other way cost every tile an extra hop for the afternoon.
|
|
163
|
+
if (known[node] === true) {
|
|
164
|
+
verdicts[node] = true;
|
|
147
165
|
return Promise.resolve();
|
|
148
166
|
}
|
|
149
167
|
return probe(route).then((reachable) => { verdicts[node] = reachable; });
|
|
@@ -202,5 +220,30 @@ window.PlexoraRouting = (function () {
|
|
|
202
220
|
pending.delete(datasource);
|
|
203
221
|
}
|
|
204
222
|
|
|
205
|
-
|
|
223
|
+
/**
|
|
224
|
+
* The routing table this page is currently using, or null.
|
|
225
|
+
*
|
|
226
|
+
* Synchronous and never resolving anything: it answers "what were the
|
|
227
|
+
* tiles on screen built from", which is a fact about the past. The globe
|
|
228
|
+
* compares it against `/resource_routing`'s current answer to tell whether
|
|
229
|
+
* the open project is still addressed to where a node was before it was
|
|
230
|
+
* reconnected.
|
|
231
|
+
*/
|
|
232
|
+
function held(datasource) {
|
|
233
|
+
return settled.get(datasource) || null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Ask again from scratch: drop the memoised table AND the remembered
|
|
238
|
+
* verdicts, then resolve anew. What a reconnect calls -- the node came
|
|
239
|
+
* back on a new port with a new token, so both halves of what this tab
|
|
240
|
+
* remembers are about an address that has gone.
|
|
241
|
+
*/
|
|
242
|
+
function refresh(datasource) {
|
|
243
|
+
forget(datasource);
|
|
244
|
+
return load(datasource);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { load, tileSource, unreachable, forget, held, refresh,
|
|
248
|
+
PROBE_TIMEOUT_MS };
|
|
206
249
|
})();
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
awaits it before it builds a single tile source, and every page that
|
|
35
35
|
can open a viewer loads it. Costs one request that answers "{}" for a
|
|
36
36
|
project with nothing on a node. -->
|
|
37
|
-
<script src="{{ data.base_url }}/client/src/js/services/resourceRouting.js?v=
|
|
37
|
+
<script src="{{ data.base_url }}/client/src/js/services/resourceRouting.js?v=20260830_reconnect_repair" type="text/javascript"></script>
|
|
38
38
|
<!-- Why a layer is missing, when one is. Asks /resource_status once per
|
|
39
39
|
project load and says nothing at all for a project with everything on
|
|
40
40
|
this machine, which is every ordinary one. -->
|
|
@@ -87,7 +87,7 @@
|
|
|
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=
|
|
90
|
+
<script src="{{ data.base_url }}/client/src/js/services/remoteState.js?v=20260830_reconnect_repair" 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>
|
|
@@ -97,7 +97,7 @@
|
|
|
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=
|
|
100
|
+
<script src="{{ data.base_url }}/client/src/js/services/remoteGlobe.js?v=20260830_reconnect_repair" 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. #}
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
by the time the user clicks the sidebar's upload icon, so deferred is ample.
|
|
245
245
|
#}
|
|
246
246
|
<script src="{{ data.base_url }}/client/src/js/views/channelNamesUpload.js?v=20260829_channel_names_switch" defer></script>
|
|
247
|
-
<script src="{{ data.base_url }}/client/src/js/main.js?v=
|
|
247
|
+
<script src="{{ data.base_url }}/client/src/js/main.js?v=20260830_reconnect_repair" defer></script>
|
|
248
248
|
{% else %}
|
|
249
249
|
<div id="quick_view_landing" class="quick-view-landing">
|
|
250
250
|
<div class="quick-view-card">
|
|
@@ -222,15 +222,17 @@ class RemoteSession:
|
|
|
222
222
|
|
|
223
223
|
def __init__(self, remote, *, askpass_url=None, auth_token=None,
|
|
224
224
|
timeout=None, kind=KIND_VIEWER, allow_origin=None,
|
|
225
|
-
register=None):
|
|
225
|
+
register=None, unregister=None):
|
|
226
226
|
self.remote = remote
|
|
227
227
|
self.name = remote.name
|
|
228
228
|
self.kind = kind
|
|
229
|
-
#: Node sessions only: the origin the browser will use,
|
|
230
|
-
#:
|
|
231
|
-
#: the
|
|
229
|
+
#: Node sessions only: the origin the browser will use, the callable
|
|
230
|
+
#: that records the node once it answers, and the one that takes it
|
|
231
|
+
#: back off the map when this session ends on its own. All supplied by
|
|
232
|
+
#: the route, which is the only place that knows any of them.
|
|
232
233
|
self._allow_origin = allow_origin
|
|
233
234
|
self._register = register
|
|
235
|
+
self._unregister = unregister
|
|
234
236
|
self.state = STATE_CONNECTING
|
|
235
237
|
#: What establishment last said it was doing, kept separately because a
|
|
236
238
|
#: prompt covers it over for as long as the question is on screen and
|
|
@@ -501,7 +503,12 @@ class RemoteSession:
|
|
|
501
503
|
self.session.establish()
|
|
502
504
|
except BaseException as exc: # noqa: BLE001 - reported, never raised
|
|
503
505
|
self._fail(exc)
|
|
504
|
-
|
|
506
|
+
# Not only the helper: establishment spawns real ssh processes
|
|
507
|
+
# before it can fail -- under srun, a job holding an allocation and
|
|
508
|
+
# a tunnel beside it -- and a failed connection's children serve
|
|
509
|
+
# nobody. Left running, they held the Slurm job (and its walltime
|
|
510
|
+
# bill) until the app exited.
|
|
511
|
+
self._tidy_after_end()
|
|
505
512
|
return
|
|
506
513
|
|
|
507
514
|
with self._lock:
|
|
@@ -528,6 +535,47 @@ class RemoteSession:
|
|
|
528
535
|
with self._lock:
|
|
529
536
|
if self.state not in (STATE_FAILED,):
|
|
530
537
|
self.state = STATE_EXITED
|
|
538
|
+
stopping = self._stopping
|
|
539
|
+
# `wait()` returning without stop() having been called means the
|
|
540
|
+
# connection died on its own -- a walltime, a dropped network, a crash
|
|
541
|
+
# on the far side -- and nothing else is going to run the teardown.
|
|
542
|
+
# When stop() IS what unblocked the wait, the teardown has already run
|
|
543
|
+
# (and for a disconnect, the route forgets the node itself).
|
|
544
|
+
if not stopping:
|
|
545
|
+
self._tidy_after_end()
|
|
546
|
+
|
|
547
|
+
def _tidy_after_end(self):
|
|
548
|
+
"""Teardown for a session nothing will press Disconnect on.
|
|
549
|
+
|
|
550
|
+
Three things only stop() used to do, each of which a session that
|
|
551
|
+
failed or exited on its own left behind:
|
|
552
|
+
|
|
553
|
+
- The sibling watchers. Under srun the tunnel is a second ssh: the job
|
|
554
|
+
leg exiting (which is what ends `wait()`) does not end it, and on a
|
|
555
|
+
cluster that does not adopt compute-side ssh into the job it kept a
|
|
556
|
+
local port listening and forwarding into refused connections.
|
|
557
|
+
- The askpass helper's temp directory.
|
|
558
|
+
- The node entry this session registered. The node ran inside the job
|
|
559
|
+
that just ended, so the address on the map is dead -- and while the
|
|
560
|
+
entry stood, `/resource_routing` kept offering it to browsers and
|
|
561
|
+
`/resource_status` reported a project reading from it as fine. Only
|
|
562
|
+
an entry THIS session put there (`session.registered`), through the
|
|
563
|
+
route-supplied callable that re-checks `managed_by` -- an entry a
|
|
564
|
+
terminal's own `plexora connect` owns is not this session's to take
|
|
565
|
+
down.
|
|
566
|
+
"""
|
|
567
|
+
try:
|
|
568
|
+
if self.session is not None:
|
|
569
|
+
self.session.stop()
|
|
570
|
+
except Exception:
|
|
571
|
+
pass
|
|
572
|
+
self._cleanup_helper()
|
|
573
|
+
if (self.kind == KIND_NODE and self._unregister is not None
|
|
574
|
+
and getattr(self.session, "registered", None) is not None):
|
|
575
|
+
try:
|
|
576
|
+
self._unregister(self.node_name)
|
|
577
|
+
except Exception:
|
|
578
|
+
pass
|
|
531
579
|
|
|
532
580
|
def _fail(self, exc):
|
|
533
581
|
with self._lock:
|
|
@@ -804,7 +852,7 @@ def find_by_nonce(nonce):
|
|
|
804
852
|
|
|
805
853
|
|
|
806
854
|
def start(remote, *, askpass_url=None, auth_token=None, timeout=None,
|
|
807
|
-
kind=KIND_VIEWER, allow_origin=None, register=None):
|
|
855
|
+
kind=KIND_VIEWER, allow_origin=None, register=None, unregister=None):
|
|
808
856
|
"""Begin connecting to `remote`, or say why not."""
|
|
809
857
|
key = _key(kind, remote.name)
|
|
810
858
|
with _REGISTRY_LOCK:
|
|
@@ -822,8 +870,18 @@ def start(remote, *, askpass_url=None, auth_token=None, timeout=None,
|
|
|
822
870
|
session = RemoteSession(remote, askpass_url=askpass_url,
|
|
823
871
|
auth_token=auth_token, timeout=timeout,
|
|
824
872
|
kind=kind, allow_origin=allow_origin,
|
|
825
|
-
register=register)
|
|
873
|
+
register=register, unregister=unregister)
|
|
826
874
|
_SESSIONS[key] = session
|
|
875
|
+
if existing is not None:
|
|
876
|
+
# Outside the lock -- stopping waits on processes. A failed or exited
|
|
877
|
+
# session's stop() is usually a no-op by now (its own teardown ran),
|
|
878
|
+
# but replacing the record without it leaked its watcher entries into
|
|
879
|
+
# connect._ACTIVE for the life of the app, and any child process that
|
|
880
|
+
# happened to survive its session with them.
|
|
881
|
+
try:
|
|
882
|
+
existing.stop()
|
|
883
|
+
except Exception:
|
|
884
|
+
pass
|
|
827
885
|
return session.start()
|
|
828
886
|
|
|
829
887
|
|
|
@@ -789,7 +789,12 @@ def settings_remotes_connect(name):
|
|
|
789
789
|
if kind == remote_sessions.KIND_NODE:
|
|
790
790
|
# Read here, in the request, because both are facts about the browser
|
|
791
791
|
# that made it -- and the thread that uses them has no request to ask.
|
|
792
|
-
|
|
792
|
+
# `unregister` is the other half of `register`: when the session ends
|
|
793
|
+
# on its own -- a walltime, a dropped network -- the node it put on
|
|
794
|
+
# the map is dead, and the session's own teardown is the only thing
|
|
795
|
+
# left that knows to take it off.
|
|
796
|
+
extra = {"allow_origin": _browser_origin(), "register": _record_node,
|
|
797
|
+
"unregister": _forget_node_entry}
|
|
793
798
|
try:
|
|
794
799
|
session = remote_sessions.start(
|
|
795
800
|
remote,
|
|
@@ -837,12 +842,28 @@ def _forget_node(name):
|
|
|
837
842
|
an address they can fix, and is none of this route's business.
|
|
838
843
|
"""
|
|
839
844
|
try:
|
|
840
|
-
from plexora import nodes as node_api
|
|
841
|
-
from plexora.server.models import nodes as node_registry
|
|
842
845
|
from plexora.server.models import remotes as remote_store
|
|
843
846
|
|
|
844
847
|
remote = remote_store.find(name)
|
|
845
848
|
node_name = (remote.node_name or name) if remote is not None else name
|
|
849
|
+
_forget_node_entry(node_name)
|
|
850
|
+
except Exception:
|
|
851
|
+
pass
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
def _forget_node_entry(node_name):
|
|
855
|
+
"""Drop one registry entry, if a saved connection is what wrote it.
|
|
856
|
+
|
|
857
|
+
The half of `_forget_node` that acts on a NODE name rather than a profile
|
|
858
|
+
name. Shared with the session's own teardown (`remote_sessions.start`'s
|
|
859
|
+
`unregister`), which already knows the node's name and has no request to
|
|
860
|
+
resolve a profile from. Never fatal, and guarded by the same `managed_by`
|
|
861
|
+
proof: an entry somebody registered by hand is theirs.
|
|
862
|
+
"""
|
|
863
|
+
try:
|
|
864
|
+
from plexora import nodes as node_api
|
|
865
|
+
from plexora.server.models import nodes as node_registry
|
|
866
|
+
|
|
846
867
|
entry = node_registry.load_all().get(node_name)
|
|
847
868
|
if entry is not None and entry.managed_by == f"connect:{node_name}":
|
|
848
869
|
node_api.forget_node(node_name)
|