stemtrace 0.3.6__tar.gz → 0.3.7__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.
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.bumpversion.toml +16 -1
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.dockerignore +16 -5
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.github/dependabot.yml +15 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.github/workflows/ci.yml +2 -2
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.github/workflows/e2e.yml +1 -1
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.github/workflows/release.yml +2 -2
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.pre-commit-config.yaml +5 -3
- {stemtrace-0.3.6 → stemtrace-0.3.7}/CHANGELOG.md +17 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/Dockerfile +3 -2
- {stemtrace-0.3.6 → stemtrace-0.3.7}/PKG-INFO +19 -3
- {stemtrace-0.3.6 → stemtrace-0.3.7}/README.md +18 -2
- {stemtrace-0.3.6 → stemtrace-0.3.7}/pyproject.toml +2 -6
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/__init__.py +21 -49
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/core/graph.py +238 -5
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/signals.py +11 -4
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/__main__.py +22 -41
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/api/__init__.py +2 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/api/routes.py +64 -36
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/api/schemas.py +6 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/api/websocket.py +11 -6
- stemtrace-0.3.7/src/stemtrace/server/fastapi/form_auth.py +342 -0
- stemtrace-0.3.7/src/stemtrace/server/fastapi/form_auth_middleware.py +154 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/fastapi/login_routes.py +14 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/store.py +145 -60
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/biome.json +2 -2
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/package-lock.json +128 -136
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/package.json +6 -6
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/api/client.ts +115 -21
- stemtrace-0.3.7/src/stemtrace/server/ui/frontend/src/components/LogoutButton.tsx +20 -0
- stemtrace-0.3.7/src/stemtrace/server/ui/frontend/src/components/SessionRejectedNotice.tsx +31 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +67 -28
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +7 -2
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +27 -20
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/main.tsx +3 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +29 -14
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +14 -55
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +2 -0
- stemtrace-0.3.7/src/stemtrace/server/ui/frontend/tests/auth.spec.ts +119 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +2 -0
- stemtrace-0.3.7/src/stemtrace/server/ui/frontend/tests/header.spec.ts +186 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +119 -1
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/static.py +22 -75
- stemtrace-0.3.7/tests/conftest.py +97 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/e2e/conftest.py +44 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/e2e/test_api_e2e.py +14 -4
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/e2e/test_workers_e2e.py +1 -1
- stemtrace-0.3.7/tests/unit/test_cli_form_login.py +280 -0
- stemtrace-0.3.7/tests/unit/test_form_auth.py +389 -0
- stemtrace-0.3.7/tests/unit/test_form_login.py +808 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_graph.py +626 -1
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_routes.py +280 -12
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_signals.py +28 -3
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_store.py +238 -2
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_ui_static_prefix.py +39 -4
- {stemtrace-0.3.6 → stemtrace-0.3.7}/uv.lock +592 -329
- stemtrace-0.3.6/src/stemtrace/server/fastapi/form_auth.py +0 -157
- stemtrace-0.3.6/tests/conftest.py +0 -18
- stemtrace-0.3.6/tests/unit/test_form_auth.py +0 -135
- stemtrace-0.3.6/tests/unit/test_form_login.py +0 -293
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.gitignore +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/.python-version +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/CONTRIBUTING.md +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/Dockerfile.e2e +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/LICENSE +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/Makefile +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/build_ui.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docker-compose.e2e.rabbitmq.yml +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docker-compose.e2e.yml +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docker-compose.rabbitmq.yml +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docker-compose.yml +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docs/brand/stemtrace-mark-light.svg +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docs/brand/stemtrace-mark.svg +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docs/screenshots/task_details.png +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docs/screenshots/unregistered.png +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/docs/screenshots/workflow.png +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/examples/celery_app.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/examples/fastapi_integration.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/examples/with_auth.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/examples/with_login.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/progress.md +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/scripts/wait_for_http.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/core/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/core/events.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/core/exceptions.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/core/ports.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/bootsteps.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/config.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/scrubbing.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/transports/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/transports/memory.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/transports/rabbitmq.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/library/transports/redis.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/py.typed +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/consumer.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/fastapi/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/fastapi/auth.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/fastapi/extension.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/fastapi/router.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/index.html +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/SproutMark.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/ui/templates/login.html +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/src/stemtrace/server/websocket.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/e2e/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/e2e/tasks.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_embedded_static.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_fastapi_integration.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_rabbitmq_transport.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_redis_resilience_docker.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_redis_transport.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_static_ui.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_wait_for_http_redirects.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/integration/test_workers_refresh.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/__init__.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_auth.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_bootsteps.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_build_ui_hook.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_config.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_consumer.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_events.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_examples.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_exceptions.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_favicon.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_init.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_redis_resilience.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_scrubbing.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_transports.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_wait_for_http_script.py +0 -0
- {stemtrace-0.3.6 → stemtrace-0.3.7}/tests/unit/test_websocket.py +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[tool.bumpversion]
|
|
2
|
-
current_version = "0.3.
|
|
2
|
+
current_version = "0.3.7"
|
|
3
3
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
4
4
|
serialize = ["{major}.{minor}.{patch}"]
|
|
5
5
|
commit = true
|
|
@@ -24,6 +24,21 @@ filename = "src/stemtrace/server/ui/frontend/package.json"
|
|
|
24
24
|
search = '"version": "{current_version}"'
|
|
25
25
|
replace = '"version": "{new_version}"'
|
|
26
26
|
|
|
27
|
+
# Frontend package-lock.json: root version and the root package entry
|
|
28
|
+
[[tool.bumpversion.files]]
|
|
29
|
+
filename = "src/stemtrace/server/ui/frontend/package-lock.json"
|
|
30
|
+
search = """"name": "stemtrace-ui",
|
|
31
|
+
"version": "{current_version}\""""
|
|
32
|
+
replace = """"name": "stemtrace-ui",
|
|
33
|
+
"version": "{new_version}\""""
|
|
34
|
+
|
|
35
|
+
[[tool.bumpversion.files]]
|
|
36
|
+
filename = "src/stemtrace/server/ui/frontend/package-lock.json"
|
|
37
|
+
search = """"name": "stemtrace-ui",
|
|
38
|
+
"version": "{current_version}\""""
|
|
39
|
+
replace = """"name": "stemtrace-ui",
|
|
40
|
+
"version": "{new_version}\""""
|
|
41
|
+
|
|
27
42
|
# uv.lock - keep workspace lockfile in sync (uv stores the local project version)
|
|
28
43
|
[[tool.bumpversion.files]]
|
|
29
44
|
filename = "uv.lock"
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
.git
|
|
3
3
|
.gitignore
|
|
4
4
|
|
|
5
|
-
# Python
|
|
6
|
-
__pycache__
|
|
7
|
-
|
|
5
|
+
# Python (patterns without **/ only match at the context root)
|
|
6
|
+
**/__pycache__
|
|
7
|
+
**/*.py[cod]
|
|
8
8
|
*$py.class
|
|
9
9
|
*.so
|
|
10
10
|
.Python
|
|
@@ -37,13 +37,23 @@ ENV/
|
|
|
37
37
|
|
|
38
38
|
# Testing
|
|
39
39
|
.pytest_cache/
|
|
40
|
+
.mypy_cache/
|
|
41
|
+
.ruff_cache/
|
|
40
42
|
.coverage
|
|
41
43
|
htmlcov/
|
|
42
44
|
.tox/
|
|
43
45
|
.nox/
|
|
44
46
|
|
|
45
47
|
# Node (rebuilt in container)
|
|
46
|
-
node_modules/
|
|
48
|
+
**/node_modules/
|
|
49
|
+
|
|
50
|
+
# Frontend build output and test artifacts (the UI is built in the
|
|
51
|
+
# frontend stage; a local dist/ would be merged into the image's copy)
|
|
52
|
+
src/stemtrace/server/ui/frontend/dist/
|
|
53
|
+
src/stemtrace/server/ui/frontend/test-results/
|
|
54
|
+
src/stemtrace/server/ui/frontend/playwright-report/
|
|
55
|
+
src/stemtrace/server/ui/frontend/.tanstack/
|
|
56
|
+
src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo
|
|
47
57
|
|
|
48
58
|
# Documentation
|
|
49
59
|
docs/_build/
|
|
@@ -53,9 +63,10 @@ docs/_build/
|
|
|
53
63
|
.env.local
|
|
54
64
|
*.log
|
|
55
65
|
|
|
56
|
-
# Cursor/Editor
|
|
66
|
+
# Cursor/Editor/agent tooling
|
|
57
67
|
.cursor/
|
|
58
68
|
.context/
|
|
69
|
+
.claude/
|
|
59
70
|
|
|
60
71
|
# Keep these (they're built in Dockerfile)
|
|
61
72
|
!src/stemtrace/server/ui/frontend/package*.json
|
|
@@ -7,6 +7,10 @@ updates:
|
|
|
7
7
|
interval: weekly
|
|
8
8
|
commit-message:
|
|
9
9
|
prefix: "chore(deps)"
|
|
10
|
+
ignore:
|
|
11
|
+
# Dependabot's uv updater fails on the build backend; updated by the
|
|
12
|
+
# weekly maintenance routine
|
|
13
|
+
- dependency-name: "hatchling"
|
|
10
14
|
|
|
11
15
|
- package-ecosystem: npm
|
|
12
16
|
directory: /src/stemtrace/server/ui/frontend
|
|
@@ -14,6 +18,10 @@ updates:
|
|
|
14
18
|
interval: weekly
|
|
15
19
|
commit-message:
|
|
16
20
|
prefix: "chore(deps)"
|
|
21
|
+
ignore:
|
|
22
|
+
# Pinned to ^22: types must match the Node 22 runtime used in CI and Docker
|
|
23
|
+
- dependency-name: "@types/node"
|
|
24
|
+
update-types: ["version-update:semver-major"]
|
|
17
25
|
|
|
18
26
|
- package-ecosystem: github-actions
|
|
19
27
|
directory: /
|
|
@@ -29,3 +37,10 @@ updates:
|
|
|
29
37
|
interval: weekly
|
|
30
38
|
commit-message:
|
|
31
39
|
prefix: "chore(deps)"
|
|
40
|
+
ignore:
|
|
41
|
+
# The shipped Python runtime is deliberate: stay on 3.12.x patch updates
|
|
42
|
+
- dependency-name: "python"
|
|
43
|
+
update-types: ["version-update:semver-major", "version-update:semver-minor"]
|
|
44
|
+
# UI is built with Node 22 LTS: allow minor/patch updates only
|
|
45
|
+
- dependency-name: "node"
|
|
46
|
+
update-types: ["version-update:semver-major"]
|
|
@@ -135,7 +135,7 @@ jobs:
|
|
|
135
135
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
136
136
|
|
|
137
137
|
- name: Set up Node.js
|
|
138
|
-
uses: actions/setup-node@
|
|
138
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
139
139
|
with:
|
|
140
140
|
node-version: "22"
|
|
141
141
|
cache: "npm"
|
|
@@ -170,7 +170,7 @@ jobs:
|
|
|
170
170
|
enable-cache: true
|
|
171
171
|
|
|
172
172
|
- name: Set up Node.js
|
|
173
|
-
uses: actions/setup-node@
|
|
173
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
174
174
|
with:
|
|
175
175
|
node-version: "22"
|
|
176
176
|
cache: "npm"
|
|
@@ -153,7 +153,7 @@ jobs:
|
|
|
153
153
|
sleep 5
|
|
154
154
|
|
|
155
155
|
- name: Set up Node.js
|
|
156
|
-
uses: actions/setup-node@
|
|
156
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
157
157
|
with:
|
|
158
158
|
node-version: "22"
|
|
159
159
|
cache: "npm"
|
|
@@ -56,7 +56,7 @@ jobs:
|
|
|
56
56
|
python-version: "3.12"
|
|
57
57
|
|
|
58
58
|
- name: Set up Node.js
|
|
59
|
-
uses: actions/setup-node@
|
|
59
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
60
60
|
with:
|
|
61
61
|
node-version: "22"
|
|
62
62
|
cache: "npm"
|
|
@@ -112,7 +112,7 @@ jobs:
|
|
|
112
112
|
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
113
113
|
|
|
114
114
|
- name: Set up QEMU
|
|
115
|
-
uses: docker/setup-qemu-action@
|
|
115
|
+
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
|
|
116
116
|
|
|
117
117
|
- name: Set up Docker Buildx
|
|
118
118
|
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
|
|
@@ -11,13 +11,15 @@ repos:
|
|
|
11
11
|
- id: debug-statements
|
|
12
12
|
|
|
13
13
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
14
|
-
rev: v0.
|
|
14
|
+
rev: v0.16.10
|
|
15
15
|
hooks:
|
|
16
16
|
# Linter (auto-fix enabled)
|
|
17
|
-
- id: ruff
|
|
17
|
+
- id: ruff-check
|
|
18
18
|
args: [--fix]
|
|
19
|
-
# Formatter
|
|
19
|
+
# Formatter (Python only: the hook also formats Markdown code blocks,
|
|
20
|
+
# which would undo the hand-aligned comments in the README examples)
|
|
20
21
|
- id: ruff-format
|
|
22
|
+
types_or: [python, pyi]
|
|
21
23
|
|
|
22
24
|
# Local hooks use project environment (uv) for proper dependency access
|
|
23
25
|
- repo: local
|
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.7] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Docker: the image's `HEALTHCHECK` now calls the new `<prefix>/api/health/live` endpoint instead of `/stemtrace/api/health`, which returns 401 under form login (so containers with login enabled reported unhealthy). The liveness endpoint needs no session and checks only that the process is serving: it always answers 200, with an informational `{"status": "ok"}`, or `{"status": "degraded"}` by the same rule as `/api/health` (the event consumer stopped unexpectedly or can't read from the broker; a consumer stopped on purpose or whose event source finished is not degraded).
|
|
14
|
+
- Docker image: updated the locked runtime dependencies `fastapi` 0.141.1 → 0.142.2 (which now pulls in `opentelemetry-api` 1.45.1; stemtrace doesn't configure tracing, so it stays a no-op), `pydantic` 2.12.5 → 2.13.5 (`pydantic-core` 2.41.5 → 2.46.5), `celery` 5.6.0 → 5.6.3, `typer` 0.21.0 → 0.27.3, `typing-extensions` 4.15.0 → 4.16.0 and `websockets` 15.0.1 → 16.1.1 (used by uvicorn's WebSocket support). Only the lockfile changed: the package's dependency ranges are the same, so pip installs are unaffected. With typer 0.27, `--help` shows option types as `<str>`/`<int>` instead of `TEXT`/`INTEGER`.
|
|
15
|
+
- Docker image: building the image from a local checkout no longer copies the frontend's `node_modules/`, `dist/` or test artifacts into the build context, so stale local UI assets can't end up in the image.
|
|
16
|
+
- UI: updated `@tanstack/react-query` 5.94.5 → 5.104.1 in the bundled frontend.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Form login: clear warnings and an in-app explanation when sessions are rejected because workers/replicas don't share `STEMTRACE_LOGIN_SECRET`. Without an explicit secret each process signs cookies with its own random secret, so under `uvicorn --workers N`, gunicorn or several replicas a cookie issued by one process is rejected by the others (random 401s, unstyled UI, login loops). stemtrace now logs a startup warning when form login runs without a secret (mentioning `WEB_CONCURRENCY` > 1 or gunicorn when detected). In that mode, when a well-formed, unexpired session cookie fails signature verification and the issue time it carries (`iat`, now included in the signed session payload; unverified in this case and used only to classify the rejection, never to grant access) is after the process started (so it most likely came from a sibling worker or replica), it also logs a rate-limited warning (also for rejected WebSocket handshakes) and returns `"reason": "session_secret_mismatch"` in API 401 responses, and HTML routes redirect to the login page with `reason=session_mismatch`, which shows an explanation. Cookies from before a restart, and any badly signed cookie when a secret is configured, get a plain 401 / login redirect.
|
|
20
|
+
- Form login: the static UI bundle under `/assets/` (the public open-source JS/CSS build, no data) is now served without a session, so the UI always loads and is styled instead of rendering a blank or unstyled page when a bundle request is rejected. The UI routes, API and WebSocket stay protected, and paths containing `.` or `..` segments (including percent-encoded variants), or an encoded `?` or `#` (`%3F`, `%23`), are rejected with 404 before any exemption applies. Exemptions are decided on the decoded path that routing matches, so e.g. `/api/health/live%3Fx` can't pass as the liveness probe. Paths under `assets/` that aren't real bundle files return 404 instead of the app shell.
|
|
21
|
+
- UI: the Logout control is now a button in the header instead of a floating overlay injected by the server, which covered the connection status. On narrow screens the header wraps onto extra rows instead of overflowing, and the status tooltip stays inside the viewport.
|
|
22
|
+
- UI: a 401 from the API (missing or expired session) now redirects to the login page with `next` set to the current page, instead of showing "Failed to load ..." errors. A 401 with `reason: "session_secret_mismatch"` shows an explanation instead, since signing in again wouldn't help. Redirects happen at most once per page and not again within a few seconds of the last one, which prevents redirect loops. The WebSocket reconnect now backs off exponentially (3s up to 60s) instead of retrying every 3s forever.
|
|
23
|
+
- Task state no longer regresses when events arrive out of order. The sender-side `PENDING` event (from `task_sent`, which fires after publish) could reach the stream after the worker's `STARTED`/`SUCCESS`, and the task showed as `PENDING` again. Node state now comes from the most advanced event, and the result is the same for every arrival order. A terminal state (`SUCCESS`/`FAILURE`/`REVOKED`/`REJECTED`) always beats a non-terminal one, and between terminals the later one wins. Among other states, a newer attempt (higher `retries`) wins, then lifecycle order (`PENDING` < `RECEIVED` < `STARTED`/`RETRY`), with `STARTED` vs `RETRY` in the same attempt decided by time (a redelivered run after a connection-loss retry shows as `STARTED`). A task's events are kept in lifecycle order: the first attempt's `PENDING` then `RECEIVED` first (even when stamped after the task was received or finished), then everything else by timestamp, with a fixed order for identical timestamps. `first_seen`, `last_updated`, `duration_ms`, date filters, sorting and eviction follow that order instead of arrival order. Node aliases from task arguments (`node_alias_from_arguments`) are read from the first event that carries arguments, so a leading `RECEIVED` (which has none) no longer hides them. Task lists, task details and graphs are read from a single snapshot taken under the store lock, so a concurrent event can't make a response mix old and new data (e.g. a `?state=STARTED` filter returning a task that just finished). Naive event timestamps are treated as UTC. Exact duplicate events are ignored and not re-broadcast over the WebSocket.
|
|
24
|
+
- UI: the task timeline and event history follow the same order as the API. They used to sort by retry count first, so a failure raised in the worker main process (hard time limit, lost worker), which Celery reports with `retries=0`, appeared before later attempts and produced negative durations. A late-stamped `PENDING` is shown first, with its time clamped to the first run.
|
|
25
|
+
- The signal handler no longer raises `TypeError` when Celery cancels a running task (lost broker connection, cold shutdown): Celery sends `task_retry` without a `reason` on that path. The `RETRY` event is now created, with the exception text "cancelled by Celery". During a broker outage it still can't be delivered if stemtrace publishes through that same broker.
|
|
26
|
+
|
|
10
27
|
## [0.3.6] - 2026-10-10
|
|
11
28
|
|
|
12
29
|
### Security
|
|
@@ -60,9 +60,10 @@ ENV PORT="8000"
|
|
|
60
60
|
|
|
61
61
|
EXPOSE 8000
|
|
62
62
|
|
|
63
|
-
# Health check (CLI server mounts API at /stemtrace prefix)
|
|
63
|
+
# Health check (CLI server mounts API at /stemtrace prefix). The liveness
|
|
64
|
+
# endpoint needs no login session and answers 200 while the process serves.
|
|
64
65
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
65
|
-
CMD python -c "import httpx; httpx.get('http://localhost:8000/stemtrace/api/health').raise_for_status()"
|
|
66
|
+
CMD python -c "import httpx; httpx.get('http://localhost:8000/stemtrace/api/health/live').raise_for_status()"
|
|
66
67
|
|
|
67
68
|
# Default command: run server
|
|
68
69
|
ENTRYPOINT ["stemtrace"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stemtrace
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: A lightweight Celery task flow visualizer
|
|
5
5
|
Project-URL: Homepage, https://github.com/iansokolskyi/stemtrace
|
|
6
6
|
Project-URL: Documentation, https://github.com/iansokolskyi/stemtrace#readme
|
|
@@ -59,7 +59,7 @@ Description-Content-Type: text/markdown
|
|
|
59
59
|
|
|
60
60
|
**Zero-infrastructure Celery task flow visualizer**
|
|
61
61
|
|
|
62
|
-
[](https://pypi.org/project/stemtrace)
|
|
63
63
|
[](https://pypi.org/project/stemtrace/)
|
|
64
64
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
65
65
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -443,6 +443,10 @@ stemtrace server \
|
|
|
443
443
|
--login-secret change-me
|
|
444
444
|
```
|
|
445
445
|
|
|
446
|
+
Set `--login-secret` (or `STEMTRACE_LOGIN_SECRET`) to a long random value, and use the same
|
|
447
|
+
value on every replica. Without it, each process generates its own random secret, so sessions
|
|
448
|
+
only work within a single process and are lost on restart.
|
|
449
|
+
|
|
446
450
|
#### High-Scale Production Setup
|
|
447
451
|
|
|
448
452
|
Note: `stemtrace server` includes an embedded consumer today (single-process). A multi-process deployment mode is planned.
|
|
@@ -512,7 +516,19 @@ stemtrace.init_app(
|
|
|
512
516
|
)
|
|
513
517
|
```
|
|
514
518
|
|
|
515
|
-
This serves a sign-in page at `/stemtrace/login` and protects
|
|
519
|
+
This serves a sign-in page at `/stemtrace/login` and protects the UI, API and WebSocket under
|
|
520
|
+
`/stemtrace`. The static JS/CSS bundle under `/stemtrace/assets/` is the public UI build and is
|
|
521
|
+
served without a session. So is `/stemtrace/api/health/live`, a liveness probe for load
|
|
522
|
+
balancers and container health checks: it always answers 200 while the process is serving,
|
|
523
|
+
with only `{"status": "ok"}`, or `{"status": "degraded"}` when the embedded consumer stopped
|
|
524
|
+
unexpectedly or can't read from the broker (the same rule as `/stemtrace/api/health`).
|
|
525
|
+
|
|
526
|
+
For any multi-process deployment (`uvicorn --workers N`, gunicorn, or several replicas behind
|
|
527
|
+
a load balancer), set `login_secret` (or the `STEMTRACE_LOGIN_SECRET` env var) to the same long
|
|
528
|
+
random value everywhere, e.g. `python -c "import secrets; print(secrets.token_urlsafe(32))"`.
|
|
529
|
+
Without it, each process signs sessions with its own random secret, and users get logged out
|
|
530
|
+
at random. stemtrace logs a warning at startup in that case, and the login page and UI explain
|
|
531
|
+
the problem when a session signed by another process is rejected.
|
|
516
532
|
|
|
517
533
|
#### Built-in Auth Helpers (Basic / API key)
|
|
518
534
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Zero-infrastructure Celery task flow visualizer**
|
|
6
6
|
|
|
7
|
-
[](https://pypi.org/project/stemtrace)
|
|
8
8
|
[](https://pypi.org/project/stemtrace/)
|
|
9
9
|
[](https://github.com/iansokolskyi/stemtrace/actions/workflows/ci.yml)
|
|
10
10
|
[](https://codecov.io/gh/iansokolskyi/stemtrace)
|
|
@@ -388,6 +388,10 @@ stemtrace server \
|
|
|
388
388
|
--login-secret change-me
|
|
389
389
|
```
|
|
390
390
|
|
|
391
|
+
Set `--login-secret` (or `STEMTRACE_LOGIN_SECRET`) to a long random value, and use the same
|
|
392
|
+
value on every replica. Without it, each process generates its own random secret, so sessions
|
|
393
|
+
only work within a single process and are lost on restart.
|
|
394
|
+
|
|
391
395
|
#### High-Scale Production Setup
|
|
392
396
|
|
|
393
397
|
Note: `stemtrace server` includes an embedded consumer today (single-process). A multi-process deployment mode is planned.
|
|
@@ -457,7 +461,19 @@ stemtrace.init_app(
|
|
|
457
461
|
)
|
|
458
462
|
```
|
|
459
463
|
|
|
460
|
-
This serves a sign-in page at `/stemtrace/login` and protects
|
|
464
|
+
This serves a sign-in page at `/stemtrace/login` and protects the UI, API and WebSocket under
|
|
465
|
+
`/stemtrace`. The static JS/CSS bundle under `/stemtrace/assets/` is the public UI build and is
|
|
466
|
+
served without a session. So is `/stemtrace/api/health/live`, a liveness probe for load
|
|
467
|
+
balancers and container health checks: it always answers 200 while the process is serving,
|
|
468
|
+
with only `{"status": "ok"}`, or `{"status": "degraded"}` when the embedded consumer stopped
|
|
469
|
+
unexpectedly or can't read from the broker (the same rule as `/stemtrace/api/health`).
|
|
470
|
+
|
|
471
|
+
For any multi-process deployment (`uvicorn --workers N`, gunicorn, or several replicas behind
|
|
472
|
+
a load balancer), set `login_secret` (or the `STEMTRACE_LOGIN_SECRET` env var) to the same long
|
|
473
|
+
random value everywhere, e.g. `python -c "import secrets; print(secrets.token_urlsafe(32))"`.
|
|
474
|
+
Without it, each process signs sessions with its own random secret, and users get logged out
|
|
475
|
+
at random. stemtrace logs a warning at startup in that case, and the login page and UI explain
|
|
476
|
+
the problem when a session signed by another process is rejected.
|
|
461
477
|
|
|
462
478
|
#### Built-in Auth Helpers (Basic / API key)
|
|
463
479
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "stemtrace"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.7"
|
|
4
4
|
description = "A lightweight Celery task flow visualizer"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -115,10 +115,6 @@ ignore_missing_imports = true
|
|
|
115
115
|
module = "kombu.*"
|
|
116
116
|
ignore_missing_imports = true
|
|
117
117
|
|
|
118
|
-
[[tool.mypy.overrides]]
|
|
119
|
-
module = "amqp.*"
|
|
120
|
-
ignore_missing_imports = true
|
|
121
|
-
|
|
122
118
|
# =============================================================================
|
|
123
119
|
# ruff
|
|
124
120
|
# =============================================================================
|
|
@@ -204,6 +200,6 @@ exclude_lines = [
|
|
|
204
200
|
"if TYPE_CHECKING:",
|
|
205
201
|
"raise NotImplementedError",
|
|
206
202
|
"@abstractmethod",
|
|
207
|
-
"
|
|
203
|
+
"^\\s*\\.\\.\\.\\s*$", # Protocol method stubs (a bare `...` body line)
|
|
208
204
|
"class.*Protocol.*:",
|
|
209
205
|
]
|
|
@@ -16,9 +16,6 @@ Usage:
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
import os
|
|
19
|
-
import secrets
|
|
20
|
-
import urllib.parse
|
|
21
|
-
from collections.abc import Awaitable, Callable
|
|
22
19
|
from typing import TYPE_CHECKING, Any
|
|
23
20
|
|
|
24
21
|
from stemtrace.core.events import TaskEvent, TaskState
|
|
@@ -36,14 +33,15 @@ from stemtrace.server.fastapi import (
|
|
|
36
33
|
require_api_key,
|
|
37
34
|
require_basic_auth,
|
|
38
35
|
)
|
|
39
|
-
from stemtrace.server.fastapi.form_auth import FormAuthConfig,
|
|
36
|
+
from stemtrace.server.fastapi.form_auth import FormAuthConfig, resolve_session_secret
|
|
37
|
+
from stemtrace.server.fastapi.form_auth_middleware import create_form_auth_middleware
|
|
40
38
|
from stemtrace.server.fastapi.login_routes import create_login_router
|
|
41
39
|
|
|
42
40
|
if TYPE_CHECKING:
|
|
43
41
|
from celery import Celery
|
|
44
42
|
from fastapi import FastAPI
|
|
45
43
|
|
|
46
|
-
__version__ = "0.3.
|
|
44
|
+
__version__ = "0.3.7"
|
|
47
45
|
__all__ = [
|
|
48
46
|
"ConfigurationError",
|
|
49
47
|
"StemtraceConfig",
|
|
@@ -178,7 +176,10 @@ def init_app(
|
|
|
178
176
|
login_password: Enable built-in form login if set (password for login_username).
|
|
179
177
|
login_secret: Secret used to sign session cookies. If not provided, uses
|
|
180
178
|
STEMTRACE_LOGIN_SECRET env var. If still missing, a random secret is
|
|
181
|
-
generated
|
|
179
|
+
generated per process and a warning is logged: sessions then only
|
|
180
|
+
work within one process (and are invalidated on restart), so set the
|
|
181
|
+
same long random value on every worker/replica of a multi-process
|
|
182
|
+
deployment.
|
|
182
183
|
login_ttl_seconds: Session TTL in seconds (default: 24 hours).
|
|
183
184
|
node_alias_from_arguments: Derive graph node display name from task arguments.
|
|
184
185
|
Digit string for args[index], string for kwargs[key], or None (default).
|
|
@@ -208,10 +209,8 @@ def init_app(
|
|
|
208
209
|
effective_login_username = login_username or os.getenv("STEMTRACE_LOGIN_USERNAME")
|
|
209
210
|
effective_login_password = login_password or os.getenv("STEMTRACE_LOGIN_PASSWORD")
|
|
210
211
|
if effective_login_username and effective_login_password:
|
|
211
|
-
effective_secret = (
|
|
212
|
-
login_secret
|
|
213
|
-
or os.getenv("STEMTRACE_LOGIN_SECRET")
|
|
214
|
-
or secrets.token_urlsafe(32)
|
|
212
|
+
effective_secret, secret_is_generated = resolve_session_secret(
|
|
213
|
+
login_secret or os.getenv("STEMTRACE_LOGIN_SECRET")
|
|
215
214
|
)
|
|
216
215
|
form_auth_config = FormAuthConfig(
|
|
217
216
|
username=effective_login_username,
|
|
@@ -220,6 +219,7 @@ def init_app(
|
|
|
220
219
|
ttl_seconds=login_ttl_seconds,
|
|
221
220
|
cookie_name="stemtrace_session",
|
|
222
221
|
cookie_path=mount_prefix,
|
|
222
|
+
secret_is_generated=secret_is_generated,
|
|
223
223
|
)
|
|
224
224
|
|
|
225
225
|
_install_stemtrace_form_auth(
|
|
@@ -256,51 +256,23 @@ def _install_stemtrace_form_auth(
|
|
|
256
256
|
The login router must be added before stemtrace UI's SPA catch-all route,
|
|
257
257
|
otherwise `/login` would be handled by the SPA fallback.
|
|
258
258
|
"""
|
|
259
|
-
from fastapi import Request
|
|
260
|
-
from fastapi.responses import JSONResponse, RedirectResponse
|
|
261
|
-
from starlette.responses import Response
|
|
262
|
-
|
|
263
259
|
login_router = create_login_router(
|
|
264
260
|
form_auth_config,
|
|
265
261
|
default_next_path=f"{mount_prefix}/",
|
|
266
262
|
)
|
|
267
263
|
app.include_router(login_router, prefix=mount_prefix)
|
|
268
264
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
# Allow login/logout without auth.
|
|
282
|
-
if path in (login_path, logout_path):
|
|
283
|
-
return await call_next(request)
|
|
284
|
-
|
|
285
|
-
cookie_value = request.cookies.get(form_auth_config.cookie_name)
|
|
286
|
-
if is_authenticated_cookie(
|
|
287
|
-
cookie_value,
|
|
288
|
-
secret=form_auth_config.secret,
|
|
289
|
-
expected_username=form_auth_config.username,
|
|
290
|
-
):
|
|
291
|
-
return await call_next(request)
|
|
292
|
-
|
|
293
|
-
# Not authenticated.
|
|
294
|
-
if path.startswith(f"{mount_prefix}/api"):
|
|
295
|
-
return JSONResponse({"detail": "Authentication required"}, status_code=401)
|
|
296
|
-
if path.startswith(f"{mount_prefix}/assets"):
|
|
297
|
-
return JSONResponse({"detail": "Authentication required"}, status_code=401)
|
|
298
|
-
|
|
299
|
-
next_target = path
|
|
300
|
-
if request.url.query:
|
|
301
|
-
next_target = f"{path}?{request.url.query}"
|
|
302
|
-
next_qs = urllib.parse.urlencode({"next": next_target})
|
|
303
|
-
return RedirectResponse(url=f"{login_path}?{next_qs}", status_code=303)
|
|
265
|
+
app.middleware("http")(
|
|
266
|
+
create_form_auth_middleware(
|
|
267
|
+
form_auth_config,
|
|
268
|
+
login_path=f"{mount_prefix}/login",
|
|
269
|
+
logout_path=f"{mount_prefix}/logout",
|
|
270
|
+
api_prefix=f"{mount_prefix}/api",
|
|
271
|
+
assets_prefix=f"{mount_prefix}/assets",
|
|
272
|
+
protected_prefix=mount_prefix,
|
|
273
|
+
public_paths=(f"{mount_prefix}/api/health/live",),
|
|
274
|
+
)
|
|
275
|
+
)
|
|
304
276
|
|
|
305
277
|
|
|
306
278
|
def _reset() -> None:
|