portacode 1.4.15.dev26__tar.gz → 1.4.16__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.
- {portacode-1.4.15.dev26 → portacode-1.4.16}/PKG-INFO +1 -1
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/_version.py +2 -2
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +40 -16
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/manager.py +25 -3
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/proxmox_infra.py +365 -5
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/terminal.py +80 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode.egg-info/PKG-INFO +1 -1
- {portacode-1.4.15.dev26 → portacode-1.4.16}/.claude/agents/communication-manager.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/.claude/settings.local.json +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/.gitignore +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/.gitmodules +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/LICENSE +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/MANIFEST.in +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/Makefile +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/backup.sh +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/connect.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/connect.sh +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/docker-compose.yaml +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/docs/images/device-transfer-button.png +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/docs/images/device-transfer-modal.png +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/docs/images/pair-device-button.png +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/docs/images/pairing-request.png +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/docs/images/student-workspace.png +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/simple_device/Dockerfile +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/simple_device/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/simple_device/data/device-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/simple_device/docker-compose.yaml +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/Dockerfile +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/.local/share/portacode/run/gateway.pid +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/.gitignore +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/db.sqlite3 +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/settings.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/__pycache__/wsgi.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/admin.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/apps.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/menu.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/models.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/urls.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/__pycache__/views.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/migrations/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-01/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-02/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-03/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-04/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-05/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-06/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-07/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-08/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-09/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/.local/share/portacode/keys/id_portacode.pub +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/data/student-10/workspace/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/docker-compose.yaml +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/galactic_bakeshop/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/galactic_bakeshop/asgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/galactic_bakeshop/settings.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/galactic_bakeshop/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/galactic_bakeshop/wsgi.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/manage.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/templates/treats/home.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/admin.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/apps.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/menu.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/migrations/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/urls.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/initial_content/treats/views.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/examples/workshop_fleet/instructions/WELCOME.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/__main__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/cli.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/client.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/base.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/chunked_content.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/diff_handlers.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/file_handlers.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_aware_file_handlers.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/file_system_watcher.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/git_manager.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/handlers.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/models.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/utils.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state_handlers.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/registry.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/session.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/system_handlers.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/tab_factory.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/terminal_handlers.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/test_proxmox_infra.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/update_handler.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/multiplex.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/data.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/keypair.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/elinks +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/gio-open +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/gnome-open +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/gvfs-open +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/kde-open +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/kfmclient +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/link_capture_exec.sh +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/link_capture_wrapper.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/links +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/links2 +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/lynx +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/mate-open +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/netsurf +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/sensible-browser +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/w3m +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/x-www-browser +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/link_capture/bin/xdg-open +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/logging_categories.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/pairing.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/service.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/static/js/test-ntp-clock.html +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/static/js/utils/ntp-clock.js +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/utils/NTP_ARCHITECTURE.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/utils/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/utils/diff_apply.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/utils/diff_renderer.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/utils/ntp_clock.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode.egg-info/SOURCES.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode.egg-info/dependency_links.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode.egg-info/entry_points.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode.egg-info/requires.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/portacode.egg-info/top_level.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/pyproject.toml +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/restore.sh +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/run_tests.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/setup.cfg +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/setup.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test.sh +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_device_online.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_file_operations.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_git_status_ui.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_login_flow.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_navigate_testing_folder.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_play_store_screenshots.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_terminal_buffer_performance.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_terminal_interaction.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_terminal_loading_race_condition.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_modules/test_terminal_start.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/test_request_id.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/.env.example +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/README.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/cli.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/__init__.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/base_test.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/cli_manager.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/hierarchical_runner.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/playwright_manager.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/runner.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/shared_cli_manager.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/core/test_discovery.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/testing_framework/requirements.txt +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/UI_UX/opening_a_file_on_desktop_results_in_nothing.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/agent_context_management.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/django_server_time_sync.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/device_performance_degradation.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/git_data_not_captured_in_proxmox.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/indefinite_resource_loading.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/portacode_service_silently_down.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/premature_terminal_exit.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/project_cpu_hotspots.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/terminals_exit_upon_starting.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/issues/wrong_item_classification_on_client_side.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/todo/smartphone_terminal_input_frustrations.md +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/tools/generate_play_store_assets.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/tools/pairing_tester.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/tools/run_screenshot_suite.sh +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/tools/test_python_ntp_clock.py +0 -0
- {portacode-1.4.15.dev26 → portacode-1.4.16}/validate.sh +0 -0
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.4.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4,
|
|
31
|
+
__version__ = version = '1.4.16'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 16)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
{portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/WEBSOCKET_PROTOCOL.md
RENAMED
|
@@ -381,6 +381,7 @@ Starts a previously provisioned, Portacode-managed LXC container. Handled by [`S
|
|
|
381
381
|
**Payload Fields:**
|
|
382
382
|
|
|
383
383
|
* `ctid` (string, required): Identifier of the container to start.
|
|
384
|
+
* `child_device_id` (string, required): Dashboard `Device.id` of the container that triggered the request; the handler validates the CT belongs to that device before issuing the start.
|
|
384
385
|
|
|
385
386
|
**Responses:**
|
|
386
387
|
|
|
@@ -394,6 +395,7 @@ Stops a running Portacode-managed container. Handled by [`StopProxmoxContainerHa
|
|
|
394
395
|
**Payload Fields:**
|
|
395
396
|
|
|
396
397
|
* `ctid` (string, required): Identifier of the container to stop.
|
|
398
|
+
* `child_device_id` (string, required): Dashboard `Device.id` that owns the container; the handler rejects the request if the CT is mapped to another device.
|
|
397
399
|
|
|
398
400
|
**Responses:**
|
|
399
401
|
|
|
@@ -407,6 +409,7 @@ Deletes a managed container from Proxmox (stopping it first if necessary) and re
|
|
|
407
409
|
**Payload Fields:**
|
|
408
410
|
|
|
409
411
|
* `ctid` (string, required): Identifier of the container to delete.
|
|
412
|
+
* `child_device_id` (string, required): Dashboard `Device.id` that should own the container metadata being purged.
|
|
410
413
|
|
|
411
414
|
**Responses:**
|
|
412
415
|
|
|
@@ -1133,22 +1136,43 @@ Provides system information in response to a `system_info` action. Handled by [`
|
|
|
1133
1136
|
* `bridge` (string): The bridge interface configured (typically `vmbr1`).
|
|
1134
1137
|
* `health` (string|null): `"healthy"` when the connectivity verification succeeded.
|
|
1135
1138
|
* `node_status` (object|null): Status response returned by the Proxmox API when validating the token.
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1139
|
+
* `managed_containers` (object): Cached summary of the Portacode-managed containers:
|
|
1140
|
+
* `updated_at` (string): ISO timestamp when this snapshot was last refreshed.
|
|
1141
|
+
* `count` (integer): Number of managed containers.
|
|
1142
|
+
* `total_ram_mib` (integer): RAM footprint summed across all containers.
|
|
1143
|
+
* `total_disk_gib` (integer): Disk footprint summed across all containers.
|
|
1144
|
+
* `total_cpu_share` (number): CPU shares requested across all containers.
|
|
1145
|
+
* `containers` (array[object]): Container summaries with the following fields:
|
|
1146
|
+
* `vmid` (string|null): Numeric CT ID.
|
|
1147
|
+
* `hostname` (string|null): Hostname configured in the CT.
|
|
1148
|
+
* `template` (string|null): Template identifier used.
|
|
1149
|
+
* `storage` (string|null): Storage pool backing the rootfs.
|
|
1150
|
+
* `disk_gib` (integer): Rootfs size in GiB.
|
|
1151
|
+
* `ram_mib` (integer): Memory size in MiB.
|
|
1152
|
+
* `cpu_share` (number): vCPU-equivalent share requested at creation.
|
|
1153
|
+
* `status` (string): Lowercase lifecycle status (e.g., `running`, `stopped`, `deleted`).
|
|
1154
|
+
* `created_at` (string|null): ISO timestamp recorded when the CT was provisioned.
|
|
1155
|
+
* `managed` (boolean): `true` for Portacode-managed entries.
|
|
1156
|
+
* `matches_default_storage` (boolean): `true` when this container is backed by the default storage pool used for new Portacode containers.
|
|
1157
|
+
* `type` (string): Either `lxc` or `qemu`, indicating whether we enumerated the container from the LXC or QEMU APIs.
|
|
1158
|
+
* `unmanaged_containers` (array[object]): Facts about containers Portacode did not provision; fields mirror the managed list but are marked `managed=false`.
|
|
1159
|
+
* `reserve_on_boot` (boolean): `true` when the CT is configured to start at boot; this flag is used to decide if its RAM and CPU allocations count toward the available totals.
|
|
1160
|
+
* `unmanaged_count` (integer): Number of unmanaged containers detected on the node.
|
|
1161
|
+
* `allocated_ram_mib` (integer): Total RAM reserved by both managed and unmanaged containers.
|
|
1162
|
+
* `allocated_disk_gib` (integer): Total disk reserved by both managed and unmanaged containers.
|
|
1163
|
+
* `allocated_cpu_share` (number): Total CPU shares requested by both managed and unmanaged containers.
|
|
1164
|
+
* `available_ram_mib` (integer|null): Remaining RAM after subtracting all reservations from the host total (null when unavailable).
|
|
1165
|
+
* `available_disk_gib` (integer|null): Remaining disk GB after subtracting allocations from the host total.
|
|
1166
|
+
* `available_cpu_share` (number|null): Remaining CPU shares after allocations.
|
|
1167
|
+
* `host_total_ram_mib` (integer|null): Host memory capacity observed via Proxmox.
|
|
1168
|
+
* `host_total_disk_gib` (integer|null): Host disk capacity observed via Proxmox.
|
|
1169
|
+
* `host_total_cpu_cores` (integer|null): Number of CPU cores reported by Proxmox.
|
|
1170
|
+
* `default_storage` (string|null): Storage pool name selected during infrastructure configuration.
|
|
1171
|
+
* `default_storage_snapshot` (object|null): Fresh stats for the default storage pool:
|
|
1172
|
+
* `storage` (string): Storage pool identifier.
|
|
1173
|
+
* `total_gib` (integer|null): Capacity of the storage pool.
|
|
1174
|
+
* `avail_gib` (integer|null): Available space remaining.
|
|
1175
|
+
* `used_gib` (integer|null): Space already consumed.
|
|
1152
1176
|
* `portacode_version` (string): Installed CLI version returned by `portacode.__version__`.
|
|
1153
1177
|
|
|
1154
1178
|
### `proxmox_infra_configured`
|
{portacode-1.4.15.dev26 → portacode-1.4.16}/portacode/connection/handlers/project_state/manager.py
RENAMED
|
@@ -21,6 +21,24 @@ from .git_manager import GitManager
|
|
|
21
21
|
from .file_system_watcher import FileSystemWatcher
|
|
22
22
|
from ....logging_categories import get_categorized_logger, LogCategory
|
|
23
23
|
|
|
24
|
+
def _deterministic_file_tab_id(file_path: str) -> str:
|
|
25
|
+
try:
|
|
26
|
+
resolved = os.path.abspath(file_path)
|
|
27
|
+
mtime = int(Path(resolved).stat().st_mtime)
|
|
28
|
+
except OSError:
|
|
29
|
+
mtime = "unknown"
|
|
30
|
+
resolved = os.path.abspath(file_path)
|
|
31
|
+
return f"{resolved}:{mtime}"
|
|
32
|
+
|
|
33
|
+
def _deterministic_diff_tab_id(
|
|
34
|
+
file_path: str,
|
|
35
|
+
from_ref: str,
|
|
36
|
+
to_ref: str,
|
|
37
|
+
from_hash: Optional[str],
|
|
38
|
+
to_hash: Optional[str]
|
|
39
|
+
) -> str:
|
|
40
|
+
return f"diff:{os.path.abspath(file_path)}:{from_ref}:{to_ref}:{from_hash or ''}:{to_hash or ''}"
|
|
41
|
+
|
|
24
42
|
logger = get_categorized_logger(__name__)
|
|
25
43
|
|
|
26
44
|
# Global singleton instance
|
|
@@ -657,10 +675,10 @@ class ProjectStateManager:
|
|
|
657
675
|
# Create new file tab using tab factory
|
|
658
676
|
from ..tab_factory import get_tab_factory
|
|
659
677
|
tab_factory = get_tab_factory()
|
|
660
|
-
|
|
678
|
+
|
|
661
679
|
try:
|
|
662
680
|
logger.info(f"About to create tab for file: {file_path}")
|
|
663
|
-
new_tab = await tab_factory.create_file_tab(file_path)
|
|
681
|
+
new_tab = await tab_factory.create_file_tab(file_path, tab_id=_deterministic_file_tab_id(file_path))
|
|
664
682
|
logger.info(f"Tab created successfully, adding to project state")
|
|
665
683
|
project_state.open_tabs[tab_key] = new_tab
|
|
666
684
|
if set_active:
|
|
@@ -842,7 +860,11 @@ class ProjectStateManager:
|
|
|
842
860
|
diff_title = f"{os.path.basename(file_path)} ({' '.join(title_parts)})"
|
|
843
861
|
|
|
844
862
|
diff_tab = await tab_factory.create_diff_tab_with_title(
|
|
845
|
-
file_path,
|
|
863
|
+
file_path,
|
|
864
|
+
original_content,
|
|
865
|
+
modified_content,
|
|
866
|
+
diff_title,
|
|
867
|
+
tab_id=_deterministic_diff_tab_id(file_path, from_ref, to_ref, from_hash, to_hash),
|
|
846
868
|
diff_details=diff_details
|
|
847
869
|
)
|
|
848
870
|
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import logging
|
|
8
8
|
import math
|
|
9
9
|
import os
|
|
10
|
+
import re
|
|
10
11
|
import secrets
|
|
11
12
|
import shlex
|
|
12
13
|
import shutil
|
|
@@ -248,6 +249,217 @@ def _pick_storage(storages: Iterable[Dict[str, Any]]) -> str:
|
|
|
248
249
|
return candidates[0].get("storage", "")
|
|
249
250
|
|
|
250
251
|
|
|
252
|
+
def _bytes_to_gib(value: Any) -> float:
|
|
253
|
+
try:
|
|
254
|
+
return float(value) / 1024**3
|
|
255
|
+
except (TypeError, ValueError):
|
|
256
|
+
return 0.0
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _bytes_to_mib(value: Any) -> float:
|
|
260
|
+
try:
|
|
261
|
+
return float(value) / 1024**2
|
|
262
|
+
except (TypeError, ValueError):
|
|
263
|
+
return 0.0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _size_token_to_gib(token: str) -> float:
|
|
267
|
+
match = re.match(r"^\s*([0-9]+(?:\.[0-9]+)?)\s*([KMGTP])?([iI]?[bB])?\s*$", token)
|
|
268
|
+
if not match:
|
|
269
|
+
return 0.0
|
|
270
|
+
number = float(match.group(1))
|
|
271
|
+
unit = (match.group(2) or "").upper()
|
|
272
|
+
scale = {
|
|
273
|
+
"": 1,
|
|
274
|
+
"K": 1024**1,
|
|
275
|
+
"M": 1024**2,
|
|
276
|
+
"G": 1024**3,
|
|
277
|
+
"T": 1024**4,
|
|
278
|
+
"P": 1024**5,
|
|
279
|
+
}.get(unit, 1)
|
|
280
|
+
return (number * scale) / 1024**3
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _extract_size_gib(value: Any) -> float:
|
|
284
|
+
if not value:
|
|
285
|
+
return 0.0
|
|
286
|
+
text = str(value)
|
|
287
|
+
for part in text.split(","):
|
|
288
|
+
if "size=" in part:
|
|
289
|
+
token = part.split("=", 1)[1]
|
|
290
|
+
return _size_token_to_gib(token)
|
|
291
|
+
return _size_token_to_gib(text)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _extract_storage_token(value: Any) -> str:
|
|
295
|
+
if not value:
|
|
296
|
+
return "unknown"
|
|
297
|
+
text = str(value)
|
|
298
|
+
if ":" in text:
|
|
299
|
+
return text.split(":", 1)[0].strip() or "unknown"
|
|
300
|
+
return text.strip() or "unknown"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _storage_from_lxc(cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
|
|
304
|
+
rootfs = cfg.get("rootfs") or entry.get("rootfs")
|
|
305
|
+
storage = _extract_storage_token(rootfs)
|
|
306
|
+
if storage != "unknown":
|
|
307
|
+
return storage
|
|
308
|
+
for idx in range(0, 10):
|
|
309
|
+
mp_value = cfg.get(f"mp{idx}")
|
|
310
|
+
storage = _extract_storage_token(mp_value)
|
|
311
|
+
if storage != "unknown":
|
|
312
|
+
return storage
|
|
313
|
+
return "unknown"
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _storage_from_qemu(cfg: Dict[str, Any]) -> str:
|
|
317
|
+
preferred_keys: List[str] = []
|
|
318
|
+
for prefix in ("scsi", "virtio", "sata", "ide"):
|
|
319
|
+
preferred_keys.extend(f"{prefix}{idx}" for idx in range(0, 6))
|
|
320
|
+
seen = set()
|
|
321
|
+
for key in preferred_keys:
|
|
322
|
+
value = cfg.get(key)
|
|
323
|
+
if value is None:
|
|
324
|
+
continue
|
|
325
|
+
seen.add(key)
|
|
326
|
+
text = str(value)
|
|
327
|
+
if "media=cdrom" in text or "cloudinit" in text:
|
|
328
|
+
continue
|
|
329
|
+
storage = _extract_storage_token(text)
|
|
330
|
+
if storage != "unknown":
|
|
331
|
+
return storage
|
|
332
|
+
for key in sorted(cfg.keys()):
|
|
333
|
+
if key in seen:
|
|
334
|
+
continue
|
|
335
|
+
if not any(key.startswith(prefix) for prefix in ("scsi", "virtio", "sata", "ide")):
|
|
336
|
+
continue
|
|
337
|
+
value = cfg.get(key)
|
|
338
|
+
if value is None:
|
|
339
|
+
continue
|
|
340
|
+
text = str(value)
|
|
341
|
+
if "media=cdrom" in text or "cloudinit" in text:
|
|
342
|
+
continue
|
|
343
|
+
storage = _extract_storage_token(text)
|
|
344
|
+
if storage != "unknown":
|
|
345
|
+
return storage
|
|
346
|
+
for key in ("efidisk0", "tpmstate0"):
|
|
347
|
+
storage = _extract_storage_token(cfg.get(key))
|
|
348
|
+
if storage != "unknown":
|
|
349
|
+
return storage
|
|
350
|
+
return "unknown"
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _primary_lxc_disk(cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
|
|
354
|
+
return str(cfg.get("rootfs") or entry.get("rootfs") or "")
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _primary_qemu_disk(cfg: Dict[str, Any]) -> str:
|
|
358
|
+
preferred_keys: List[str] = []
|
|
359
|
+
for prefix in ("scsi", "virtio", "sata", "ide"):
|
|
360
|
+
preferred_keys.extend(f"{prefix}{idx}" for idx in range(0, 6))
|
|
361
|
+
seen = set()
|
|
362
|
+
for key in preferred_keys:
|
|
363
|
+
value = cfg.get(key)
|
|
364
|
+
if value is None:
|
|
365
|
+
continue
|
|
366
|
+
seen.add(key)
|
|
367
|
+
text = str(value)
|
|
368
|
+
if "media=cdrom" in text or "cloudinit" in text:
|
|
369
|
+
continue
|
|
370
|
+
return text
|
|
371
|
+
for key in sorted(cfg.keys()):
|
|
372
|
+
if key in seen:
|
|
373
|
+
continue
|
|
374
|
+
if not any(key.startswith(prefix) for prefix in ("scsi", "virtio", "sata", "ide")):
|
|
375
|
+
continue
|
|
376
|
+
value = cfg.get(key)
|
|
377
|
+
if value is None:
|
|
378
|
+
continue
|
|
379
|
+
text = str(value)
|
|
380
|
+
if "media=cdrom" in text or "cloudinit" in text:
|
|
381
|
+
continue
|
|
382
|
+
return text
|
|
383
|
+
return ""
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _pick_container_storage(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> str:
|
|
387
|
+
storage = _extract_storage_token(cfg.get("storage") or entry.get("storage"))
|
|
388
|
+
if storage != "unknown":
|
|
389
|
+
return storage
|
|
390
|
+
if kind == "lxc":
|
|
391
|
+
return _storage_from_lxc(cfg, entry)
|
|
392
|
+
return _storage_from_qemu(cfg)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _pick_container_disk_gib(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
|
|
396
|
+
if kind == "lxc":
|
|
397
|
+
size = _extract_size_gib(_primary_lxc_disk(cfg, entry))
|
|
398
|
+
if size:
|
|
399
|
+
return size
|
|
400
|
+
else:
|
|
401
|
+
size = _extract_size_gib(_primary_qemu_disk(cfg))
|
|
402
|
+
if size:
|
|
403
|
+
return size
|
|
404
|
+
for candidate in (entry.get("maxdisk"), entry.get("disk"), cfg.get("disk")):
|
|
405
|
+
if candidate is None or candidate == 0:
|
|
406
|
+
continue
|
|
407
|
+
return _bytes_to_gib(candidate)
|
|
408
|
+
return 0.0
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _to_mib(value: Any) -> float:
|
|
412
|
+
try:
|
|
413
|
+
val = float(value)
|
|
414
|
+
except (TypeError, ValueError):
|
|
415
|
+
return 0.0
|
|
416
|
+
if val <= 0:
|
|
417
|
+
return 0.0
|
|
418
|
+
# Heuristic: large values are bytes, smaller ones are already MiB.
|
|
419
|
+
return _bytes_to_mib(val) if val > 10000 else val
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _pick_container_ram_mib(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
|
|
423
|
+
for candidate in (cfg.get("memory"), entry.get("maxmem"), entry.get("mem")):
|
|
424
|
+
ram = _to_mib(candidate)
|
|
425
|
+
if ram:
|
|
426
|
+
return ram
|
|
427
|
+
return 0.0
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _safe_float(value: Any) -> float:
|
|
431
|
+
try:
|
|
432
|
+
return float(value)
|
|
433
|
+
except (TypeError, ValueError):
|
|
434
|
+
return 0.0
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _pick_container_cpu_share(kind: str, cfg: Dict[str, Any], entry: Dict[str, Any]) -> float:
|
|
438
|
+
if kind == "lxc":
|
|
439
|
+
for key in ("cpulimit", "cores", "cpus"):
|
|
440
|
+
val = _safe_float(cfg.get(key))
|
|
441
|
+
if val:
|
|
442
|
+
return val
|
|
443
|
+
return _safe_float(entry.get("cpus"))
|
|
444
|
+
|
|
445
|
+
cores = _safe_float(cfg.get("cores"))
|
|
446
|
+
sockets = _safe_float(cfg.get("sockets")) or 1.0
|
|
447
|
+
if cores:
|
|
448
|
+
return cores * sockets
|
|
449
|
+
val = _safe_float(cfg.get("vcpus"))
|
|
450
|
+
if val:
|
|
451
|
+
return val
|
|
452
|
+
val = _safe_float(entry.get("cpus") or entry.get("maxcpu"))
|
|
453
|
+
if val:
|
|
454
|
+
return val
|
|
455
|
+
return 0.0
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _parse_onboot_flag(value: Any) -> bool:
|
|
459
|
+
text = str(value).strip().lower()
|
|
460
|
+
return text in {"1", "true", "yes", "on"}
|
|
461
|
+
|
|
462
|
+
|
|
251
463
|
def _write_bridge_config(bridge: str) -> None:
|
|
252
464
|
begin = f"# Portacode INFRA BEGIN {bridge}"
|
|
253
465
|
end = f"# Portacode INFRA END {bridge}"
|
|
@@ -410,6 +622,7 @@ def _build_managed_containers_summary(records: List[Dict[str, Any]]) -> Dict[str
|
|
|
410
622
|
containers.append(
|
|
411
623
|
{
|
|
412
624
|
"vmid": str(_as_int(record.get("vmid"))) if record.get("vmid") is not None else None,
|
|
625
|
+
"device_id": record.get("device_id"),
|
|
413
626
|
"hostname": record.get("hostname"),
|
|
414
627
|
"template": record.get("template"),
|
|
415
628
|
"storage": record.get("storage"),
|
|
@@ -431,6 +644,139 @@ def _build_managed_containers_summary(records: List[Dict[str, Any]]) -> Dict[str
|
|
|
431
644
|
}
|
|
432
645
|
|
|
433
646
|
|
|
647
|
+
def _build_full_container_summary(records: List[Dict[str, Any]], config: Dict[str, Any]) -> Dict[str, Any]:
|
|
648
|
+
base_summary = _build_managed_containers_summary(records)
|
|
649
|
+
if not config or not config.get("token_value"):
|
|
650
|
+
return base_summary
|
|
651
|
+
|
|
652
|
+
try:
|
|
653
|
+
proxmox = _connect_proxmox(config)
|
|
654
|
+
node = _get_node_from_config(config)
|
|
655
|
+
except Exception as exc: # pragma: no cover - best effort
|
|
656
|
+
logger.debug("Unable to extend container summary with Proxmox data: %s", exc)
|
|
657
|
+
return base_summary
|
|
658
|
+
|
|
659
|
+
default_storage = (config.get("default_storage") or "").strip()
|
|
660
|
+
record_map: Dict[str, Dict[str, Any]] = {}
|
|
661
|
+
for record in records:
|
|
662
|
+
vmid = record.get("vmid")
|
|
663
|
+
if vmid is None:
|
|
664
|
+
continue
|
|
665
|
+
try:
|
|
666
|
+
vmid_key = str(int(vmid))
|
|
667
|
+
except (ValueError, TypeError):
|
|
668
|
+
continue
|
|
669
|
+
record_map[vmid_key] = record
|
|
670
|
+
|
|
671
|
+
managed_entries: List[Dict[str, Any]] = []
|
|
672
|
+
unmanaged_entries: List[Dict[str, Any]] = []
|
|
673
|
+
allocated_ram = 0.0
|
|
674
|
+
allocated_disk = 0.0
|
|
675
|
+
allocated_cpu = 0.0
|
|
676
|
+
|
|
677
|
+
def _process_entries(kind: str, getter: str) -> None:
|
|
678
|
+
nonlocal allocated_ram, allocated_disk, allocated_cpu
|
|
679
|
+
entries = getattr(proxmox.nodes(node), getter).get()
|
|
680
|
+
for entry in entries:
|
|
681
|
+
vmid = entry.get("vmid")
|
|
682
|
+
if vmid is None:
|
|
683
|
+
continue
|
|
684
|
+
vmid_str = str(vmid)
|
|
685
|
+
cfg: Dict[str, Any] = {}
|
|
686
|
+
try:
|
|
687
|
+
cfg = getattr(proxmox.nodes(node), getter)(vmid_str).config.get() or {}
|
|
688
|
+
except Exception as exc: # pragma: no cover - best effort
|
|
689
|
+
logger.debug("Failed to load %s config for %s: %s", kind, vmid_str, exc)
|
|
690
|
+
cfg = {}
|
|
691
|
+
|
|
692
|
+
record = record_map.get(vmid_str)
|
|
693
|
+
description = cfg.get("description") or ""
|
|
694
|
+
managed = bool(record) or MANAGED_MARKER in description
|
|
695
|
+
hostname = entry.get("name") or cfg.get("hostname") or (record.get("hostname") if record else None)
|
|
696
|
+
storage = _pick_container_storage(kind, cfg, entry)
|
|
697
|
+
disk_gib = _pick_container_disk_gib(kind, cfg, entry)
|
|
698
|
+
ram_mib = _pick_container_ram_mib(kind, cfg, entry)
|
|
699
|
+
cpu_share = _pick_container_cpu_share(kind, cfg, entry)
|
|
700
|
+
reserve_on_boot = _parse_onboot_flag(cfg.get("onboot"))
|
|
701
|
+
matches_default_storage = bool(default_storage and storage and storage.lower() == default_storage.lower())
|
|
702
|
+
|
|
703
|
+
base_entry = {
|
|
704
|
+
"type": kind,
|
|
705
|
+
"vmid": vmid_str,
|
|
706
|
+
"hostname": hostname,
|
|
707
|
+
"status": (entry.get("status") or "unknown").lower(),
|
|
708
|
+
"storage": storage,
|
|
709
|
+
"disk_gib": disk_gib,
|
|
710
|
+
"ram_mib": ram_mib,
|
|
711
|
+
"cpu_share": cpu_share,
|
|
712
|
+
"reserve_on_boot": reserve_on_boot,
|
|
713
|
+
"matches_default_storage": matches_default_storage,
|
|
714
|
+
"managed": managed,
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if managed:
|
|
718
|
+
merged = base_entry | {
|
|
719
|
+
"device_id": record.get("device_id") if record else None,
|
|
720
|
+
"template": record.get("template") if record else None,
|
|
721
|
+
"created_at": record.get("created_at") if record else None,
|
|
722
|
+
}
|
|
723
|
+
managed_entries.append(merged)
|
|
724
|
+
else:
|
|
725
|
+
unmanaged_entries.append(base_entry)
|
|
726
|
+
|
|
727
|
+
if managed or reserve_on_boot:
|
|
728
|
+
allocated_ram += ram_mib
|
|
729
|
+
allocated_cpu += cpu_share
|
|
730
|
+
if managed or matches_default_storage:
|
|
731
|
+
allocated_disk += disk_gib
|
|
732
|
+
|
|
733
|
+
_process_entries("lxc", "lxc")
|
|
734
|
+
_process_entries("qemu", "qemu")
|
|
735
|
+
|
|
736
|
+
memory_info = {}
|
|
737
|
+
cpu_info = {}
|
|
738
|
+
try:
|
|
739
|
+
node_status = proxmox.nodes(node).status.get()
|
|
740
|
+
memory_info = node_status.get("memory") or {}
|
|
741
|
+
cpu_info = node_status.get("cpuinfo") or {}
|
|
742
|
+
except Exception as exc: # pragma: no cover - best effort
|
|
743
|
+
logger.debug("Unable to read node status for resource totals: %s", exc)
|
|
744
|
+
|
|
745
|
+
host_total_ram_mib = _bytes_to_mib(memory_info.get("total"))
|
|
746
|
+
used_ram_mib = _bytes_to_mib(memory_info.get("used"))
|
|
747
|
+
available_ram_mib = max(host_total_ram_mib - used_ram_mib, 0.0) if host_total_ram_mib else None
|
|
748
|
+
host_total_cpu_cores = _safe_float(cpu_info.get("cores"))
|
|
749
|
+
available_cpu_share = max(host_total_cpu_cores - allocated_cpu, 0.0) if host_total_cpu_cores else None
|
|
750
|
+
|
|
751
|
+
host_total_disk_gib = None
|
|
752
|
+
available_disk_gib = None
|
|
753
|
+
if default_storage:
|
|
754
|
+
try:
|
|
755
|
+
storage_status = proxmox.nodes(node).storage(default_storage).status.get()
|
|
756
|
+
host_total_disk_gib = _bytes_to_gib(storage_status.get("total"))
|
|
757
|
+
available_disk_gib = _bytes_to_gib(storage_status.get("avail"))
|
|
758
|
+
except Exception as exc: # pragma: no cover - best effort
|
|
759
|
+
logger.debug("Unable to read storage status for %s: %s", default_storage, exc)
|
|
760
|
+
|
|
761
|
+
summary = base_summary.copy()
|
|
762
|
+
summary["containers"] = managed_entries
|
|
763
|
+
summary["count"] = len(managed_entries)
|
|
764
|
+
summary["total_ram_mib"] = int(sum(entry.get("ram_mib") or 0 for entry in managed_entries))
|
|
765
|
+
summary["total_disk_gib"] = int(sum(entry.get("disk_gib") or 0 for entry in managed_entries))
|
|
766
|
+
summary["total_cpu_share"] = round(sum(entry.get("cpu_share") or 0 for entry in managed_entries), 2)
|
|
767
|
+
summary["unmanaged_containers"] = unmanaged_entries
|
|
768
|
+
summary["allocated_ram_mib"] = round(allocated_ram, 2)
|
|
769
|
+
summary["allocated_disk_gib"] = round(allocated_disk, 2)
|
|
770
|
+
summary["allocated_cpu_share"] = round(allocated_cpu, 2)
|
|
771
|
+
summary["host_total_ram_mib"] = int(host_total_ram_mib) if host_total_ram_mib else None
|
|
772
|
+
summary["host_total_disk_gib"] = host_total_disk_gib
|
|
773
|
+
summary["host_total_cpu_cores"] = host_total_cpu_cores if host_total_cpu_cores else None
|
|
774
|
+
summary["available_ram_mib"] = int(available_ram_mib) if available_ram_mib is not None else None
|
|
775
|
+
summary["available_disk_gib"] = available_disk_gib
|
|
776
|
+
summary["available_cpu_share"] = available_cpu_share if available_cpu_share is not None else None
|
|
777
|
+
return summary
|
|
778
|
+
|
|
779
|
+
|
|
434
780
|
def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
|
|
435
781
|
def _refresh_container_statuses(records: List[Dict[str, Any]], config: Dict[str, Any] | None) -> None:
|
|
436
782
|
if not records or not config:
|
|
@@ -466,7 +812,7 @@ def _get_managed_containers_summary(force: bool = False) -> Dict[str, Any]:
|
|
|
466
812
|
config = _load_config()
|
|
467
813
|
records = _load_managed_container_records()
|
|
468
814
|
_refresh_container_statuses(records, config)
|
|
469
|
-
summary =
|
|
815
|
+
summary = _build_full_container_summary(records, config)
|
|
470
816
|
with _MANAGED_CONTAINERS_CACHE_LOCK:
|
|
471
817
|
_MANAGED_CONTAINERS_CACHE["timestamp"] = now
|
|
472
818
|
_MANAGED_CONTAINERS_CACHE["summary"] = summary
|
|
@@ -876,12 +1222,17 @@ def _parse_ctid(message: Dict[str, Any]) -> int:
|
|
|
876
1222
|
|
|
877
1223
|
|
|
878
1224
|
def _ensure_container_managed(
|
|
879
|
-
proxmox: Any, node: str, vmid: int
|
|
1225
|
+
proxmox: Any, node: str, vmid: int, *, device_id: Optional[str] = None
|
|
880
1226
|
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
881
1227
|
record = _read_container_record(vmid)
|
|
882
1228
|
ct_cfg = proxmox.nodes(node).lxc(str(vmid)).config.get()
|
|
883
1229
|
if not ct_cfg or MANAGED_MARKER not in (ct_cfg.get("description") or ""):
|
|
884
1230
|
raise RuntimeError(f"Container {vmid} is not managed by Portacode.")
|
|
1231
|
+
record_device_id = record.get("device_id")
|
|
1232
|
+
if device_id and str(record_device_id or "") != str(device_id):
|
|
1233
|
+
raise RuntimeError(
|
|
1234
|
+
f"Container {vmid} is managed for device {record_device_id!r}, not {device_id!r}."
|
|
1235
|
+
)
|
|
885
1236
|
return record, ct_cfg
|
|
886
1237
|
|
|
887
1238
|
|
|
@@ -1835,10 +2186,13 @@ class StartProxmoxContainerHandler(SyncHandler):
|
|
|
1835
2186
|
|
|
1836
2187
|
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
1837
2188
|
vmid = _parse_ctid(message)
|
|
2189
|
+
child_device_id = (message.get("child_device_id") or "").strip()
|
|
2190
|
+
if not child_device_id:
|
|
2191
|
+
raise ValueError("child_device_id is required for start_proxmox_container")
|
|
1838
2192
|
config = _ensure_infra_configured()
|
|
1839
2193
|
proxmox = _connect_proxmox(config)
|
|
1840
2194
|
node = _get_node_from_config(config)
|
|
1841
|
-
_ensure_container_managed(proxmox, node, vmid)
|
|
2195
|
+
_ensure_container_managed(proxmox, node, vmid, device_id=child_device_id)
|
|
1842
2196
|
|
|
1843
2197
|
status, elapsed = _start_container(proxmox, node, vmid)
|
|
1844
2198
|
_update_container_record(vmid, {"status": "running"})
|
|
@@ -1865,10 +2219,13 @@ class StopProxmoxContainerHandler(SyncHandler):
|
|
|
1865
2219
|
|
|
1866
2220
|
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
1867
2221
|
vmid = _parse_ctid(message)
|
|
2222
|
+
child_device_id = (message.get("child_device_id") or "").strip()
|
|
2223
|
+
if not child_device_id:
|
|
2224
|
+
raise ValueError("child_device_id is required for stop_proxmox_container")
|
|
1868
2225
|
config = _ensure_infra_configured()
|
|
1869
2226
|
proxmox = _connect_proxmox(config)
|
|
1870
2227
|
node = _get_node_from_config(config)
|
|
1871
|
-
_ensure_container_managed(proxmox, node, vmid)
|
|
2228
|
+
_ensure_container_managed(proxmox, node, vmid, device_id=child_device_id)
|
|
1872
2229
|
|
|
1873
2230
|
status, elapsed = _stop_container(proxmox, node, vmid)
|
|
1874
2231
|
final_status = status.get("status") or "stopped"
|
|
@@ -1901,10 +2258,13 @@ class RemoveProxmoxContainerHandler(SyncHandler):
|
|
|
1901
2258
|
|
|
1902
2259
|
def execute(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
1903
2260
|
vmid = _parse_ctid(message)
|
|
2261
|
+
child_device_id = (message.get("child_device_id") or "").strip()
|
|
2262
|
+
if not child_device_id:
|
|
2263
|
+
raise ValueError("child_device_id is required for remove_proxmox_container")
|
|
1904
2264
|
config = _ensure_infra_configured()
|
|
1905
2265
|
proxmox = _connect_proxmox(config)
|
|
1906
2266
|
node = _get_node_from_config(config)
|
|
1907
|
-
_ensure_container_managed(proxmox, node, vmid)
|
|
2267
|
+
_ensure_container_managed(proxmox, node, vmid, device_id=child_device_id)
|
|
1908
2268
|
|
|
1909
2269
|
stop_status, stop_elapsed = _stop_container(proxmox, node, vmid)
|
|
1910
2270
|
delete_status, delete_elapsed = _delete_container(proxmox, node, vmid)
|