PyMieSimX 0.1.1__tar.gz → 0.1.3__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.
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.coveragerc +1 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.github/workflows/deploy_PyPi.yml +5 -7
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.github/workflows/deploy_anaconda.yml +8 -9
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.github/workflows/deploy_coverage.yml +5 -2
- pymiesimx-0.1.3/.github/workflows/quality.yml +47 -0
- pymiesimx-0.1.3/.github/workflows/tests.yml +22 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.gitignore +7 -0
- pymiesimx-0.1.3/CONTRIBUTING.md +7 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/Dockerfile +2 -1
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PKG-INFO +26 -6
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/__main__.py +3 -0
- pymiesimx-0.1.3/PyMieSimX/gui/admin.py +122 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/dashboard.css +477 -44
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/default-settings.json +3 -3
- pymiesimx-0.1.3/PyMieSimX/gui/assets/home-spf-radial.gif +0 -0
- pymiesimx-0.1.3/PyMieSimX/gui/assets/home-spf-radial.png +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/callback_helpers.py +20 -3
- pymiesimx-0.1.1/PyMieSimX/gui/interface.py → pymiesimx-0.1.3/PyMieSimX/gui/callbacks.py +180 -167
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/components/cards.py +0 -1
- pymiesimx-0.1.1/PyMieSimX/gui/services.py → pymiesimx-0.1.3/PyMieSimX/gui/computation.py +142 -18
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/defaults.py +0 -1
- pymiesimx-0.1.3/PyMieSimX/gui/experiment_service.py +23 -0
- pymiesimx-0.1.3/PyMieSimX/gui/interface.py +105 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/jobs.py +3 -3
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/layout.py +100 -12
- pymiesimx-0.1.3/PyMieSimX/gui/material_catalog.py +127 -0
- pymiesimx-0.1.3/PyMieSimX/gui/models.py +64 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/experiment/page.py +62 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/experiment/sections/configuration.py +47 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/home.py +175 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/settings.py +0 -1
- pymiesimx-0.1.3/PyMieSimX/gui/pages/single/page.py +57 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/single/sections/representations.py +37 -0
- pymiesimx-0.1.3/PyMieSimX/gui/pages/single/sections/setup.py +31 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/parsing.py +3 -1
- pymiesimx-0.1.3/PyMieSimX/gui/plotting.py +19 -0
- pymiesimx-0.1.3/PyMieSimX/gui/services.py +17 -0
- pymiesimx-0.1.3/PyMieSimX/gui/single_service.py +14 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/usage_metrics.py +18 -70
- pymiesimx-0.1.3/PyMieSimX/gui/validation.py +18 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX.egg-info/PKG-INFO +26 -6
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX.egg-info/SOURCES.txt +20 -2
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX.egg-info/requires.txt +5 -1
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX.egg-info/scm_file_list.json +61 -43
- pymiesimx-0.1.3/PyMieSimX.egg-info/scm_version.json +8 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/README.rst +20 -3
- {pymiesimx-0.1.1 → pymiesimx-0.1.3/conda.recipe}/meta.yaml +3 -2
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/makefile +14 -2
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/pyproject.toml +9 -3
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/pytest.ini +2 -0
- pymiesimx-0.1.3/tests/browser/test_dashboard.py +28 -0
- pymiesimx-0.1.3/tests/gui/cli.py +57 -0
- pymiesimx-0.1.3/tests/gui/test_admin.py +77 -0
- pymiesimx-0.1.3/tests/gui/test_dash_callbacks.py +161 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/tests/gui/test_services.py +21 -2
- pymiesimx-0.1.3/tests/gui/test_usage_metrics.py +31 -0
- pymiesimx-0.1.3/tools/generate_home_spf.py +100 -0
- pymiesimx-0.1.3/tools/next_release_version.py +24 -0
- pymiesimx-0.1.3/tools/release_tag.py +27 -0
- pymiesimx-0.1.1/.github/workflows/quality.yml +0 -30
- pymiesimx-0.1.1/PyMieSimX/gui/material_catalog.py +0 -90
- pymiesimx-0.1.1/PyMieSimX/gui/pages/experiment/page.py +0 -49
- pymiesimx-0.1.1/PyMieSimX/gui/pages/experiment/sections/configuration.py +0 -42
- pymiesimx-0.1.1/PyMieSimX/gui/pages/home.py +0 -152
- pymiesimx-0.1.1/PyMieSimX/gui/pages/single/page.py +0 -28
- pymiesimx-0.1.1/PyMieSimX/gui/pages/single/sections/representations.py +0 -38
- pymiesimx-0.1.1/PyMieSimX/gui/pages/single/sections/setup.py +0 -27
- pymiesimx-0.1.1/PyMieSimX.egg-info/scm_version.json +0 -8
- pymiesimx-0.1.1/tests/gui/cli.py +0 -31
- pymiesimx-0.1.1/tests/gui/test_usage_metrics.py +0 -30
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.flake8 +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.github/dependabot.yml +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/.pre-commit-config.yaml +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/LICENSE +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/api.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/application/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/application/wsgi.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/card-state.js +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/logo.png +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/materials-stock.json +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/pymiesim-favicon.svg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/pymiesim-logo-dark.svg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/assets/pymiesim-logo.svg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/components/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/citation.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/documentation.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/experiment/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/experiment/sections/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/field_syntax.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/install_local.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/sellmeier.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/single/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/pages/single/sections/__init__.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX/gui/schemas.py +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX.egg-info/dependency_links.txt +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX.egg-info/entry_points.txt +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/PyMieSimX.egg-info/top_level.txt +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/setup.cfg +0 -0
- {pymiesimx-0.1.1 → pymiesimx-0.1.3}/tests/gui/test_api.py +0 -0
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Publish to PyPI
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
tags: ["*"]
|
|
7
|
-
pull_request:
|
|
8
|
-
branches: ["master"]
|
|
5
|
+
tags: ["v*"]
|
|
9
6
|
|
|
10
7
|
permissions:
|
|
11
8
|
contents: read
|
|
12
9
|
|
|
13
10
|
jobs:
|
|
14
|
-
|
|
15
|
-
uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@
|
|
11
|
+
publish:
|
|
12
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@v5
|
|
16
13
|
with:
|
|
17
14
|
python-version: "3.11"
|
|
15
|
+
test-command: python -c 'import PyMieSimX'
|
|
18
16
|
secrets:
|
|
19
17
|
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Publish Conda package
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
tags: '*'
|
|
7
|
-
pull_request:
|
|
8
|
-
branches: [ "master" ]
|
|
5
|
+
tags: ["v*"]
|
|
9
6
|
|
|
10
7
|
permissions:
|
|
11
|
-
|
|
8
|
+
contents: read
|
|
12
9
|
|
|
13
10
|
jobs:
|
|
14
|
-
|
|
15
|
-
uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@
|
|
11
|
+
publish:
|
|
12
|
+
uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@v5
|
|
16
13
|
with:
|
|
17
14
|
python_versions: '["3.11", "3.12", "3.13"]'
|
|
18
|
-
apt_package: ""
|
|
19
15
|
os_list: '["ubuntu-latest", "macos-latest", "windows-latest"]'
|
|
16
|
+
package_name: pymiesimx
|
|
17
|
+
recipe_path: conda.recipe
|
|
18
|
+
test_import: PyMieSimX
|
|
20
19
|
secrets:
|
|
21
20
|
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
|
|
@@ -16,11 +16,11 @@ jobs:
|
|
|
16
16
|
name: Run GUI tests and publish coverage
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
20
20
|
with:
|
|
21
21
|
fetch-depth: 0
|
|
22
22
|
|
|
23
|
-
- uses: actions/setup-python@
|
|
23
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
24
24
|
with:
|
|
25
25
|
python-version: "3.11"
|
|
26
26
|
|
|
@@ -29,6 +29,9 @@ jobs:
|
|
|
29
29
|
python -m pip install --upgrade pip wheel build
|
|
30
30
|
python -m pip install -e ".[testing]"
|
|
31
31
|
|
|
32
|
+
- name: Initialize material catalog
|
|
33
|
+
run: python -m PyOptik setup --no-progress
|
|
34
|
+
|
|
32
35
|
- name: Run GUI tests with coverage
|
|
33
36
|
run: |
|
|
34
37
|
coverage erase
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Quality and GUI tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["master"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["master"]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
python-quality:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.11", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
20
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- name: Install package and quality tools
|
|
24
|
+
run: python -m pip install --upgrade pip && python -m pip install -e ".[testing,dev]"
|
|
25
|
+
- name: Initialize material catalog
|
|
26
|
+
run: python -m PyOptik setup --no-progress
|
|
27
|
+
- name: Ruff
|
|
28
|
+
run: python -m ruff check PyMieSimX tests
|
|
29
|
+
- name: Mypy
|
|
30
|
+
run: python -m mypy PyMieSimX/gui/parsing.py PyMieSimX/gui/schemas.py PyMieSimX/gui/models.py PyMieSimX/gui/jobs.py PyMieSimX/gui/callback_helpers.py PyMieSimX/gui/admin.py
|
|
31
|
+
- name: GUI tests
|
|
32
|
+
run: python -m pytest
|
|
33
|
+
|
|
34
|
+
browser-tests:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
38
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.11"
|
|
41
|
+
- name: Install package and browser test tools
|
|
42
|
+
run: python -m pip install --upgrade pip && python -m pip install -e ".[testing,browser]"
|
|
43
|
+
- name: Initialize material catalog
|
|
44
|
+
run: python -m PyOptik setup --no-progress
|
|
45
|
+
- name: Browser workflow tests
|
|
46
|
+
# The full GUI suite enforces the repository-wide coverage threshold.
|
|
47
|
+
run: python -m pytest tests/browser --config-file=pytest.ini --headless --cov=PyMieSimX --cov-report=term --cov-fail-under=0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
tests:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
17
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
- run: python -m pip install --upgrade pip && python -m pip install -e ".[testing]"
|
|
21
|
+
- run: python -m PyOptik setup --no-progress
|
|
22
|
+
- run: python -m pytest -q
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Contributing to PyMieSimX
|
|
2
|
+
|
|
3
|
+
Use Python 3.10 or newer. Install development dependencies with `python -m pip install -e ".[testing,dev]"`, then run `make quality` and `make test`.
|
|
4
|
+
|
|
5
|
+
Releases use semantic version tags. Run `make patch`, `make minor`, or `make major` from a clean `master` checkout; the command creates the release commit, tags it, and pushes both. Publishing workflows run only for `v*` tags.
|
|
6
|
+
|
|
7
|
+
The Conda recipe is at `conda.recipe/meta.yaml`. Do not commit build artefacts, virtual environments, or caches.
|
|
@@ -7,6 +7,7 @@ WORKDIR /app
|
|
|
7
7
|
COPY . .
|
|
8
8
|
|
|
9
9
|
RUN python -m pip install --upgrade pip \
|
|
10
|
-
&& python -m pip install --no-cache-dir .
|
|
10
|
+
&& python -m pip install --no-cache-dir . \
|
|
11
|
+
&& python -m PyOptik setup --no-progress
|
|
11
12
|
|
|
12
13
|
CMD ["sh", "-c", "exec gunicorn PyMieSimX.gui.interface:app --bind 0.0.0.0:${PORT:-8050} --workers 1 --threads 2 --timeout 3600"]
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyMieSimX
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Graphical Dash interface for PyMieSim.
|
|
5
5
|
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Repository, https://github.com/MartinPdeS/PyMieSimX
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
9
|
Description-Content-Type: text/x-rst
|
|
10
10
|
License-File: LICENSE
|
|
11
|
-
Requires-Dist: PyMieSim<6,>=5.
|
|
11
|
+
Requires-Dist: PyMieSim<6,>=5.6.4
|
|
12
|
+
Requires-Dist: PyOptik<4,>=3.2.1
|
|
12
13
|
Requires-Dist: dash<5,>=4.1.0
|
|
13
14
|
Requires-Dist: gunicorn<24,>=23.0.0
|
|
14
15
|
Requires-Dist: numpy<3,>=2.0
|
|
@@ -19,6 +20,8 @@ Provides-Extra: testing
|
|
|
19
20
|
Requires-Dist: pytest>=8.0; extra == "testing"
|
|
20
21
|
Requires-Dist: pytest-cov>=5.0; extra == "testing"
|
|
21
22
|
Requires-Dist: coverage>=7.0; extra == "testing"
|
|
23
|
+
Provides-Extra: browser
|
|
24
|
+
Requires-Dist: dash[testing]<5,>=4.1; extra == "browser"
|
|
22
25
|
Provides-Extra: dev
|
|
23
26
|
Requires-Dist: build>=1.2; extra == "dev"
|
|
24
27
|
Requires-Dist: mypy==1.15.0; extra == "dev"
|
|
@@ -46,10 +49,15 @@ solver.
|
|
|
46
49
|
Installation
|
|
47
50
|
------------
|
|
48
51
|
|
|
49
|
-
Install the GUI
|
|
52
|
+
Install the GUI with PyMieSim and PyOptik with::
|
|
50
53
|
|
|
51
54
|
pip install PyMieSimX
|
|
52
55
|
|
|
56
|
+
The first ``pymiesimx`` launch downloads PyOptik's complete
|
|
57
|
+
RefractiveIndex.INFO snapshot. It can also be initialized explicitly with::
|
|
58
|
+
|
|
59
|
+
python -m PyOptik setup --no-progress
|
|
60
|
+
|
|
53
61
|
Launch the dashboard with::
|
|
54
62
|
|
|
55
63
|
pymiesimx
|
|
@@ -129,5 +137,17 @@ the Render service with::
|
|
|
129
137
|
|
|
130
138
|
PyMieSimX writes namespaced counters to the shared ``metrics_counters`` table:
|
|
131
139
|
``pymiesimx_home_page_visit_count``, ``pymiesimx_experiment_run_count``, and
|
|
132
|
-
``pymiesimx_single_run_count``.
|
|
133
|
-
|
|
140
|
+
``pymiesimx_single_run_count``. Metrics are collected only when the PostgreSQL
|
|
141
|
+
backend is explicitly configured and available. Local installations do not
|
|
142
|
+
persist usage counters.
|
|
143
|
+
|
|
144
|
+
Administration page
|
|
145
|
+
-------------------
|
|
146
|
+
|
|
147
|
+
Usage counters are shown only on the hidden, token-protected administration
|
|
148
|
+
page. Configure a server-side token with::
|
|
149
|
+
|
|
150
|
+
PYMIESIMX_ADMIN_TOKEN=<a long random value>
|
|
151
|
+
|
|
152
|
+
Then open ``/admin?token=<the same value>``. The page is intentionally absent
|
|
153
|
+
from the public sidebar and refreshes its counters every 30 seconds.
|
|
@@ -5,6 +5,9 @@ import logging
|
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
LOGGER = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
8
11
|
def _build_argument_parser() -> argparse.ArgumentParser:
|
|
9
12
|
"""Create the CLI parser for the dashboard launcher."""
|
|
10
13
|
parser = argparse.ArgumentParser(description="Launch the PyMieSimX dashboard.")
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Token-gated administration dashboard for server-side usage counters."""
|
|
2
|
+
|
|
3
|
+
import hmac
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from dash import dcc, html
|
|
10
|
+
|
|
11
|
+
from PyMieSimX.gui import usage_metrics
|
|
12
|
+
from PyMieSimX.gui.components import Card
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ADMIN_TOKEN_ENV_VAR = "PYMIESIMX_ADMIN_TOKEN"
|
|
16
|
+
REFRESH_INTERVAL_MILLISECONDS = 30_000
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class AdminDashboardData:
|
|
21
|
+
"""Snapshot displayed by the administration dashboard."""
|
|
22
|
+
|
|
23
|
+
metrics: usage_metrics.UsageMetrics = field(default_factory=usage_metrics.UsageMetrics)
|
|
24
|
+
generated_at: str = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_admin_access_granted(provided_token: Any) -> bool:
|
|
28
|
+
"""Return whether the provided token exactly matches server configuration."""
|
|
29
|
+
configured_token = os.getenv(ADMIN_TOKEN_ENV_VAR, "").strip()
|
|
30
|
+
candidate_token = str(provided_token or "").strip()
|
|
31
|
+
return bool(configured_token and candidate_token and hmac.compare_digest(configured_token, candidate_token))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def collect_admin_dashboard_data() -> AdminDashboardData:
|
|
35
|
+
"""Load the current server-side usage counters."""
|
|
36
|
+
return AdminDashboardData(
|
|
37
|
+
metrics=usage_metrics.load_usage_metrics(),
|
|
38
|
+
generated_at=f"{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def metric_text(value: object) -> str:
|
|
43
|
+
"""Format a metric for display, including unavailable local values."""
|
|
44
|
+
if isinstance(value, float) and value != value:
|
|
45
|
+
return "NaN"
|
|
46
|
+
return str(value)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def dashboard_values(data: AdminDashboardData) -> tuple[str, str, str, str]:
|
|
50
|
+
"""Return callback-ready metric values and update timestamp."""
|
|
51
|
+
metrics = data.metrics
|
|
52
|
+
return (
|
|
53
|
+
metric_text(metrics.home_page_visit_count),
|
|
54
|
+
metric_text(metrics.experiment_run_count),
|
|
55
|
+
metric_text(metrics.single_run_count),
|
|
56
|
+
f"Last updated: {data.generated_at}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_admin_page(token: str | None) -> html.Div:
|
|
61
|
+
"""Build the hidden dashboard or an intentionally generic denial page."""
|
|
62
|
+
if not is_admin_access_granted(token):
|
|
63
|
+
return html.Div(
|
|
64
|
+
html.Section(
|
|
65
|
+
className=Card.classes(color="blue", extra="admin-access-card"),
|
|
66
|
+
children=[
|
|
67
|
+
html.H2("Page not available"),
|
|
68
|
+
html.P("The requested page is not available on this server."),
|
|
69
|
+
],
|
|
70
|
+
),
|
|
71
|
+
className="page-content-stack admin-page",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
values = dashboard_values(collect_admin_dashboard_data())
|
|
75
|
+
tiles = (
|
|
76
|
+
("admin-home-page-visits", values[0], "Home page visits"),
|
|
77
|
+
("admin-experiment-runs", values[1], "Parameter sweeps"),
|
|
78
|
+
("admin-single-runs", values[2], "Particle explorations"),
|
|
79
|
+
)
|
|
80
|
+
return html.Div(
|
|
81
|
+
className="page-content-stack admin-page",
|
|
82
|
+
children=[
|
|
83
|
+
dcc.Interval(id="admin-refresh-interval", interval=REFRESH_INTERVAL_MILLISECONDS, n_intervals=0),
|
|
84
|
+
html.Section(
|
|
85
|
+
className="page-hero",
|
|
86
|
+
children=[
|
|
87
|
+
html.H1("Administration"),
|
|
88
|
+
html.P("Server-side PyMieSimX usage metrics.", className="hero-text"),
|
|
89
|
+
html.Div(
|
|
90
|
+
[
|
|
91
|
+
html.Span(values[3], id="admin-last-updated"),
|
|
92
|
+
html.Button("Refresh", id="admin-refresh-button", n_clicks=0, className="run-button"),
|
|
93
|
+
],
|
|
94
|
+
className="admin-toolbar",
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
),
|
|
98
|
+
html.Div(
|
|
99
|
+
[
|
|
100
|
+
html.Section(
|
|
101
|
+
className=Card.classes(color="blue", extra="admin-metric-card"),
|
|
102
|
+
children=[
|
|
103
|
+
html.Div(value, id=value_id, className="admin-metric-value"),
|
|
104
|
+
html.Div(label, className="admin-metric-label"),
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
for value_id, value, label in tiles
|
|
108
|
+
],
|
|
109
|
+
className="admin-metric-grid",
|
|
110
|
+
),
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
__all__ = [
|
|
116
|
+
"ADMIN_TOKEN_ENV_VAR",
|
|
117
|
+
"AdminDashboardData",
|
|
118
|
+
"build_admin_page",
|
|
119
|
+
"collect_admin_dashboard_data",
|
|
120
|
+
"dashboard_values",
|
|
121
|
+
"is_admin_access_granted",
|
|
122
|
+
]
|