celery-manager 0.1.0a2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. celery_manager-0.1.0a2/.gitignore +42 -0
  2. celery_manager-0.1.0a2/CHANGELOG.md +325 -0
  3. celery_manager-0.1.0a2/LICENSE +21 -0
  4. celery_manager-0.1.0a2/PKG-INFO +225 -0
  5. celery_manager-0.1.0a2/README.md +172 -0
  6. celery_manager-0.1.0a2/docs/assets/logo-wordmark-dark.svg +1 -0
  7. celery_manager-0.1.0a2/docs/assets/logo-wordmark-light.svg +1 -0
  8. celery_manager-0.1.0a2/docs/assets/logo.svg +1 -0
  9. celery_manager-0.1.0a2/docs/assets/social-preview.png +0 -0
  10. celery_manager-0.1.0a2/docs/configuration.md +208 -0
  11. celery_manager-0.1.0a2/docs/contributing.md +104 -0
  12. celery_manager-0.1.0a2/docs/dashboard.md +340 -0
  13. celery_manager-0.1.0a2/docs/decorator.md +275 -0
  14. celery_manager-0.1.0a2/docs/index.md +68 -0
  15. celery_manager-0.1.0a2/docs/integrations.md +176 -0
  16. celery_manager-0.1.0a2/docs/progress.md +186 -0
  17. celery_manager-0.1.0a2/docs/quickstart.md +211 -0
  18. celery_manager-0.1.0a2/docs/rest-api.md +428 -0
  19. celery_manager-0.1.0a2/examples/django_demo/.gitignore +6 -0
  20. celery_manager-0.1.0a2/examples/django_demo/README.md +203 -0
  21. celery_manager-0.1.0a2/examples/django_demo/demo/__init__.py +3 -0
  22. celery_manager-0.1.0a2/examples/django_demo/demo/asgi.py +16 -0
  23. celery_manager-0.1.0a2/examples/django_demo/demo/celery.py +15 -0
  24. celery_manager-0.1.0a2/examples/django_demo/demo/settings.py +174 -0
  25. celery_manager-0.1.0a2/examples/django_demo/demo/urls.py +27 -0
  26. celery_manager-0.1.0a2/examples/django_demo/demo/wsgi.py +16 -0
  27. celery_manager-0.1.0a2/examples/django_demo/manage.py +22 -0
  28. celery_manager-0.1.0a2/examples/django_demo/requirements.txt +7 -0
  29. celery_manager-0.1.0a2/examples/django_demo/run_dashboard.py +25 -0
  30. celery_manager-0.1.0a2/examples/django_demo/tasks_app/__init__.py +0 -0
  31. celery_manager-0.1.0a2/examples/django_demo/tasks_app/apps.py +6 -0
  32. celery_manager-0.1.0a2/examples/django_demo/tasks_app/tasks.py +89 -0
  33. celery_manager-0.1.0a2/examples/fastapi_demo/README.md +60 -0
  34. celery_manager-0.1.0a2/examples/fastapi_demo/celery_app.py +27 -0
  35. celery_manager-0.1.0a2/examples/fastapi_demo/create_user.py +19 -0
  36. celery_manager-0.1.0a2/examples/fastapi_demo/main.py +42 -0
  37. celery_manager-0.1.0a2/examples/fastapi_demo/requirements.txt +7 -0
  38. celery_manager-0.1.0a2/examples/fastapi_demo/settings.py +40 -0
  39. celery_manager-0.1.0a2/examples/fastapi_demo/tasks.py +46 -0
  40. celery_manager-0.1.0a2/examples/flask_demo/README.md +64 -0
  41. celery_manager-0.1.0a2/examples/flask_demo/app.py +66 -0
  42. celery_manager-0.1.0a2/examples/flask_demo/celery_app.py +27 -0
  43. celery_manager-0.1.0a2/examples/flask_demo/create_user.py +19 -0
  44. celery_manager-0.1.0a2/examples/flask_demo/requirements.txt +8 -0
  45. celery_manager-0.1.0a2/examples/flask_demo/settings.py +40 -0
  46. celery_manager-0.1.0a2/examples/flask_demo/tasks.py +46 -0
  47. celery_manager-0.1.0a2/frontend/.gitignore +24 -0
  48. celery_manager-0.1.0a2/frontend/.oxlintrc.json +8 -0
  49. celery_manager-0.1.0a2/frontend/README.md +86 -0
  50. celery_manager-0.1.0a2/frontend/index.html +41 -0
  51. celery_manager-0.1.0a2/frontend/package-lock.json +2442 -0
  52. celery_manager-0.1.0a2/frontend/package.json +31 -0
  53. celery_manager-0.1.0a2/frontend/postcss.config.js +6 -0
  54. celery_manager-0.1.0a2/frontend/public/favicon.svg +4 -0
  55. celery_manager-0.1.0a2/frontend/src/App.tsx +194 -0
  56. celery_manager-0.1.0a2/frontend/src/api/audit.ts +16 -0
  57. celery_manager-0.1.0a2/frontend/src/api/base.ts +10 -0
  58. celery_manager-0.1.0a2/frontend/src/api/client.ts +43 -0
  59. celery_manager-0.1.0a2/frontend/src/api/schedules.ts +57 -0
  60. celery_manager-0.1.0a2/frontend/src/api/settings.ts +103 -0
  61. celery_manager-0.1.0a2/frontend/src/api/tasks.ts +133 -0
  62. celery_manager-0.1.0a2/frontend/src/branding.tsx +382 -0
  63. celery_manager-0.1.0a2/frontend/src/lib/taskProgress.ts +147 -0
  64. celery_manager-0.1.0a2/frontend/src/main.tsx +26 -0
  65. celery_manager-0.1.0a2/frontend/src/pages/AuditLog.tsx +97 -0
  66. celery_manager-0.1.0a2/frontend/src/pages/Login.tsx +90 -0
  67. celery_manager-0.1.0a2/frontend/src/pages/ScheduleList.tsx +84 -0
  68. celery_manager-0.1.0a2/frontend/src/pages/ScheduleOccurrences.tsx +197 -0
  69. celery_manager-0.1.0a2/frontend/src/pages/Settings.tsx +369 -0
  70. celery_manager-0.1.0a2/frontend/src/pages/TaskDetail.tsx +695 -0
  71. celery_manager-0.1.0a2/frontend/src/pages/TaskList.tsx +1208 -0
  72. celery_manager-0.1.0a2/frontend/src/styles/task-detail.css +427 -0
  73. celery_manager-0.1.0a2/frontend/src/styles/tasks.css +794 -0
  74. celery_manager-0.1.0a2/frontend/src/styles/tokens.css +2139 -0
  75. celery_manager-0.1.0a2/frontend/src/theme.ts +167 -0
  76. celery_manager-0.1.0a2/frontend/tailwind.config.ts +22 -0
  77. celery_manager-0.1.0a2/frontend/tsconfig.app.json +26 -0
  78. celery_manager-0.1.0a2/frontend/tsconfig.json +7 -0
  79. celery_manager-0.1.0a2/frontend/tsconfig.node.json +23 -0
  80. celery_manager-0.1.0a2/frontend/vite.config.ts +10 -0
  81. celery_manager-0.1.0a2/mkdocs.yml +37 -0
  82. celery_manager-0.1.0a2/pyproject.toml +99 -0
  83. celery_manager-0.1.0a2/src/celery_manager/__init__.py +47 -0
  84. celery_manager-0.1.0a2/src/celery_manager/api/__init__.py +0 -0
  85. celery_manager-0.1.0a2/src/celery_manager/api/app.py +83 -0
  86. celery_manager-0.1.0a2/src/celery_manager/api/deps.py +34 -0
  87. celery_manager-0.1.0a2/src/celery_manager/api/routes/__init__.py +0 -0
  88. celery_manager-0.1.0a2/src/celery_manager/api/routes/audit.py +42 -0
  89. celery_manager-0.1.0a2/src/celery_manager/api/routes/auth.py +55 -0
  90. celery_manager-0.1.0a2/src/celery_manager/api/routes/schedules.py +72 -0
  91. celery_manager-0.1.0a2/src/celery_manager/api/routes/settings.py +83 -0
  92. celery_manager-0.1.0a2/src/celery_manager/api/routes/tasks.py +204 -0
  93. celery_manager-0.1.0a2/src/celery_manager/api/security.py +143 -0
  94. celery_manager-0.1.0a2/src/celery_manager/api/serialization.py +55 -0
  95. celery_manager-0.1.0a2/src/celery_manager/api/sse.py +89 -0
  96. celery_manager-0.1.0a2/src/celery_manager/auth/__init__.py +0 -0
  97. celery_manager-0.1.0a2/src/celery_manager/auth/sessions.py +19 -0
  98. celery_manager-0.1.0a2/src/celery_manager/auth/users.py +51 -0
  99. celery_manager-0.1.0a2/src/celery_manager/cli.py +46 -0
  100. celery_manager-0.1.0a2/src/celery_manager/config.py +38 -0
  101. celery_manager-0.1.0a2/src/celery_manager/control/__init__.py +55 -0
  102. celery_manager-0.1.0a2/src/celery_manager/control/audit.py +20 -0
  103. celery_manager-0.1.0a2/src/celery_manager/control/schedules.py +271 -0
  104. celery_manager-0.1.0a2/src/celery_manager/control/settings.py +266 -0
  105. celery_manager-0.1.0a2/src/celery_manager/control/tasks.py +319 -0
  106. celery_manager-0.1.0a2/src/celery_manager/dashboard/__init__.py +6 -0
  107. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  108. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  109. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  110. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  111. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  112. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  113. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  114. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  115. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  116. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  117. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-700-normal-Bth3BMcD.woff +0 -0
  118. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/ibm-plex-sans-latin-700-normal-Bxkt5Cjx.woff2 +0 -0
  119. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/index-AUSbcb6p.js +11 -0
  120. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/assets/index-CzsGQV47.css +1 -0
  121. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/favicon.svg +4 -0
  122. celery_manager-0.1.0a2/src/celery_manager/dashboard/static/index.html +42 -0
  123. celery_manager-0.1.0a2/src/celery_manager/db/__init__.py +0 -0
  124. celery_manager-0.1.0a2/src/celery_manager/db/models.py +124 -0
  125. celery_manager-0.1.0a2/src/celery_manager/db/session.py +34 -0
  126. celery_manager-0.1.0a2/src/celery_manager/decorator.py +451 -0
  127. celery_manager-0.1.0a2/src/celery_manager/events/__init__.py +0 -0
  128. celery_manager-0.1.0a2/src/celery_manager/events/listener.py +104 -0
  129. celery_manager-0.1.0a2/src/celery_manager/logcapture.py +19 -0
  130. celery_manager-0.1.0a2/src/celery_manager/progress.py +321 -0
  131. celery_manager-0.1.0a2/src/celery_manager/runtime.py +54 -0
  132. celery_manager-0.1.0a2/tests/__init__.py +0 -0
  133. celery_manager-0.1.0a2/tests/integration/__init__.py +0 -0
  134. celery_manager-0.1.0a2/tests/integration/test_mounting.py +153 -0
  135. celery_manager-0.1.0a2/tests/unit/__init__.py +0 -0
  136. celery_manager-0.1.0a2/tests/unit/test_api_audit.py +65 -0
  137. celery_manager-0.1.0a2/tests/unit/test_api_auth.py +201 -0
  138. celery_manager-0.1.0a2/tests/unit/test_api_schedules.py +186 -0
  139. celery_manager-0.1.0a2/tests/unit/test_api_security.py +153 -0
  140. celery_manager-0.1.0a2/tests/unit/test_api_settings.py +358 -0
  141. celery_manager-0.1.0a2/tests/unit/test_api_sse.py +251 -0
  142. celery_manager-0.1.0a2/tests/unit/test_api_tasks.py +452 -0
  143. celery_manager-0.1.0a2/tests/unit/test_auth.py +109 -0
  144. celery_manager-0.1.0a2/tests/unit/test_cli.py +38 -0
  145. celery_manager-0.1.0a2/tests/unit/test_control_retry_audit.py +246 -0
  146. celery_manager-0.1.0a2/tests/unit/test_control_schedules.py +327 -0
  147. celery_manager-0.1.0a2/tests/unit/test_control_tasks.py +260 -0
  148. celery_manager-0.1.0a2/tests/unit/test_decorator.py +642 -0
  149. celery_manager-0.1.0a2/tests/unit/test_events_listener.py +175 -0
  150. celery_manager-0.1.0a2/tests/unit/test_models.py +64 -0
  151. celery_manager-0.1.0a2/tests/unit/test_progress.py +434 -0
  152. celery_manager-0.1.0a2/tests/unit/test_setup.py +109 -0
@@ -0,0 +1,42 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ celery_manager.db
8
+ frontend/node_modules/
9
+ frontend/dist/
10
+ .pytest_cache/
11
+ venv/
12
+
13
+ # Browser-automation output from UI verification runs (screenshots,
14
+ # accessibility snapshots, console logs). Regenerated on demand; never a
15
+ # deliverable.
16
+ .playwright-mcp/
17
+
18
+ # MkDocs build output — the docs site is built from docs/ on demand.
19
+ site/
20
+
21
+ # macOS Finder metadata. Ignored here rather than relying on every
22
+ # contributor having it in their global excludes.
23
+ .DS_Store
24
+
25
+ # Dependency / tool caches
26
+ node_modules/
27
+ .ruff_cache/
28
+ .mypy_cache/
29
+ .coverage
30
+ htmlcov/
31
+
32
+ # Local-only assistant/tooling state
33
+ .superpowers/
34
+
35
+ # Demo databases
36
+ *.db
37
+ *.sqlite
38
+ *.sqlite3
39
+ *.db-journal
40
+ *.db-wal
41
+ *.db-shm
42
+ .env
@@ -0,0 +1,325 @@
1
+ # Changelog
2
+
3
+ All notable changes to celery-manager are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0a2] — 2026-10-25
8
+
9
+ The first release published to PyPI (`pip install --pre celery-manager`).
10
+ There are no code changes since 0.1.0a1 apart from the version string.
11
+
12
+ ### Changed
13
+
14
+ - README and package metadata are PyPI-ready: every link and image in the
15
+ README is now absolute, so it renders on pypi.org.
16
+ - Install instructions lead with `pip install --pre celery-manager`; the git-tag
17
+ install remains as an alternative.
18
+ - Added a `Changelog` project URL; the GitHub account casing is now
19
+ `Pythondeveloper6` everywhere.
20
+ - `SECURITY.md` updated for the now-public repository.
21
+
22
+ ## [0.1.0a1] — 2026-10-25
23
+
24
+ The initial release. Before this alpha, celery-manager had never been published, so everything
25
+ below is new rather than changed — with a few exceptions, called out first
26
+ because anyone who pinned a git SHA during development *will* hit them.
27
+
28
+ ### Breaking
29
+
30
+ - **`GET /api/tasks` now returns an envelope, not a bare array.** It used to
31
+ return a JSON array of runs, hard-capped at 50 rows, with no `offset` — so
32
+ older history was unreachable and a client could not tell "50 results" from
33
+ "the first 50 of 1,284". It now returns:
34
+
35
+ ```json
36
+ {"items": [ ... ], "total": 1284, "limit": 50, "offset": 0}
37
+ ```
38
+
39
+ Callers must change `res.json()` to `res.json().items`. The items also
40
+ gained `runtime_seconds` and the four `progress_*` fields. There is no
41
+ compatibility shim and no version negotiation: the change was taken
42
+ deliberately while the package was unreleased and its only consumers were
43
+ the bundled dashboard and `docs/rest-api.md`. See
44
+ [the REST API reference](docs/rest-api.md#tasks).
45
+ - **The session cookie is now `cm_session`, and `create_app()` refuses a
46
+ default `secret_key`.** Both are security fixes; see [Security](#security)
47
+ below for what changed and what you need to do.
48
+
49
+ ### Added
50
+
51
+ **Task history, search and filtering**
52
+
53
+ - `GET /api/tasks` gained `q`, `date_from`, `date_to`, `limit` and `offset`
54
+ alongside the existing `status` and `task_name`. `limit` is clamped into
55
+ `1..500` (`control.tasks.MAX_PAGE_SIZE`) rather than rejected, and the
56
+ clamped values are echoed back in the envelope.
57
+ - `q` is a case-insensitive substring search OR-ed across `task_name`,
58
+ `task_id`, `args` and `kwargs`. Because args/kwargs are stored as
59
+ serialized JSON text, this searches that text — there is no way to express
60
+ `kwargs.user == "alice"`. LIKE wildcards in the search term are escaped, so
61
+ `q=100%` finds a literal `100%` instead of matching every row.
62
+ - Both date bounds apply to `started_at` and are inclusive, which means a
63
+ queued run (no `started_at`) is excluded by either bound, and `date_to` is
64
+ an instant rather than a calendar day.
65
+ - `control.count_task_runs()` — the total behind "showing 50 of 1,284". It
66
+ shares one filter builder with `list_task_runs()`, so a total can never be
67
+ a count of a different query than the page it labels.
68
+
69
+ **Live progress**
70
+
71
+ - `celery_manager.report_progress(current, total=None, message=None)`, called
72
+ from inside a task body. Writes `progress_current`, `progress_total`,
73
+ `progress_message` and `progress_updated_at` on the run's `task_runs` row
74
+ while the task is still in flight; all four are served raw by both task
75
+ endpoints so the client decides how to render them.
76
+ - Database writes are throttled to one per 0.5 s per run
77
+ (`progress.MIN_WRITE_INTERVAL_SECONDS`), so it is safe in a tight loop. The
78
+ completing call (`current >= total`) bypasses the throttle, so a finished
79
+ task never sits at 99%.
80
+ - Omitting `total` means open-ended progress: a count and a message, no
81
+ percentage. A `total` of 0, negative, or non-int-able is treated the same
82
+ way — celery-manager never invents a denominator.
83
+ - Never raises into the calling task, and never blocks it: bad arguments are
84
+ ignored, a missing task context or an unwired process each log one WARNING
85
+ per process and return.
86
+ - Works on undecorated tasks too, via `celery.current_task`.
87
+
88
+ **Retry lineage**
89
+
90
+ - `task_runs.retried_from_id`, stamped by `retry_task()` on the run it
91
+ dispatches, so the chain is visible immediately rather than whenever the
92
+ events listener catches up.
93
+ - `control.list_retries_of()` and the `retried_from_id` / `retried_by` fields
94
+ on `GET /api/tasks/{task_id}`. `retried_by` is a list — retrying the same
95
+ failed run more than once is normal, and each attempt is its own run.
96
+
97
+ **Branding settings**
98
+
99
+ - `GET` / `PUT /api/settings` and the `app_settings` table, holding exactly
100
+ three keys: `brand_name`, `brand_accent`, `brand_logo_url`. Deployment-wide,
101
+ not per-browser.
102
+ - **`GET /api/settings` is deliberately unauthenticated** — the login screen
103
+ has to render the deployment's branding, and the login screen is pre-auth.
104
+ That is safe only because `DEFAULT_SETTINGS` is a closed allowlist of
105
+ non-sensitive values; `updated_by`/`updated_at` are deliberately not
106
+ returned. Treat adding a key to that dict as a security decision.
107
+ - Values are validated strictly and rejected with a `422` carrying a
108
+ human-readable reason: the accent is an anchored hex allowlist (it lands in
109
+ a CSS custom property), and the logo URL is a scheme allowlist that refuses
110
+ SVG data URIs (it lands in an `<img src>`).
111
+ - Any authenticated user can rebrand — there is no role system in v1 — but
112
+ every changed key writes an audit row with old and new values.
113
+ - `control.get_settings()` / `control.update_settings()` for the same
114
+ operations from Python.
115
+
116
+ **Task detail depth**
117
+
118
+ - `GET /api/tasks/{task_id}` gained `queue`, `worker`, `eta`, `started_at`,
119
+ `finished_at`, `runtime_seconds` and the four `progress_*` fields. (`eta`
120
+ is served but nothing currently writes it — see the note in the REST API
121
+ reference.)
122
+
123
+ **Dashboard**
124
+
125
+ - Task list rebuilt around a left filter panel (search, status, group-by,
126
+ collapsible date range) with removable active-filter chips, repeated in the
127
+ empty state so an empty table says which filter emptied it. The panel
128
+ collapses behind a **Filters** toggle below ~1024px.
129
+ - Two explicit modes: **live** (polls every 5 s, **Load more** widens the
130
+ page up to the 500-row ceiling) and **history** (explicit offset paging
131
+ through the full filtered total, polling switched off so rows can't shift
132
+ under you between clicks), with a **Back to live** bar.
133
+ - Per-row progress bars with percentage, message and a projected time
134
+ remaining; a run that has stopped reporting says how long it has been quiet
135
+ and drops the estimate rather than counting down as if nothing were wrong.
136
+ - Task detail gained a Run panel (progress, live elapsed, estimated finish),
137
+ an Execution panel (worker, queue, ETA, timings, each with a reason rather
138
+ than a blank cell), and a retry-lineage panel walking the chain in both
139
+ directions.
140
+ - Light / dark / **system** theming as an explicit per-browser choice, stored
141
+ in local storage, applied before first paint and synced across tabs.
142
+ - A branding settings screen with a live preview and a measured contrast
143
+ readout for the accent colour.
144
+ - Dashboard: sign-out control in the nav.
145
+ - Dashboard: Cancel and Terminate controls on the task detail page (terminate asks for inline confirmation)
146
+
147
+ **Framework integration**
148
+
149
+ - Mount the dashboard under a prefix inside a FastAPI app
150
+ (`app.mount("/celery-manager", dashboard)`) or a Flask app (via `a2wsgi`
151
+ and werkzeug's `DispatcherMiddleware`, new `flask` extra).
152
+ - The SPA now uses relative asset and API URLs, so it works under any mount
153
+ prefix.
154
+ - Runnable `examples/fastapi_demo` and `examples/flask_demo`.
155
+ - New docs page `docs/integrations.md`.
156
+
157
+ **Packaging and docs**
158
+
159
+ - MkDocs Material documentation site under `docs/`.
160
+ - Root `README.md`, PyPI classifiers, keywords and project URLs; `uvicorn`
161
+ moved into a `standalone` extra since mounting the ASGI app into a server
162
+ you already run needs no server of its own.
163
+ - MIT license.
164
+
165
+ ### Changed
166
+
167
+ - **`@track` now writes a `started` row before calling your task body**, not
168
+ only a terminal row afterwards. This is what makes an in-flight task
169
+ visible with no events-listener process running, and what gives
170
+ `report_progress()` a row to write to. **Cost: one extra INSERT/UPDATE and
171
+ commit per tracked run.** As with every other write in the decorator, a
172
+ failure is logged and dropped and can never change the task's outcome.
173
+ - `POST /api/schedules/{name}/occurrences/pause` now rejects
174
+ `action="reschedule"` with a `422`. It used to accept and store it, which
175
+ displayed the occurrence as paused while doing nothing — the decorator only
176
+ ever acted on `skip`/`cancel`. The column still permits the value; the
177
+ endpoint no longer does.
178
+ - The session cookie is now `Secure` by default (`cookie_secure=True`). Set
179
+ it to `False` only for local plain-HTTP development, where otherwise you
180
+ will log in successfully and then immediately appear logged out.
181
+ - `beat_schedule` entries are normalized through
182
+ `celery.schedules.maybe_schedule()`, so a bare number of seconds or a
183
+ `timedelta` works the same as an explicit `schedule()`/`crontab()`.
184
+ - `self.retry()` is now recorded as `retried` rather than `failed`. It raises
185
+ `celery.exceptions.Retry`, an ordinary `Exception` subclass, which used to
186
+ land in the decorator's failure branch and give a run Celery considers
187
+ RETRY a `failed` status, a "Retry in 30s: …" exception message and a finish
188
+ time it did not have.
189
+ - A retry attempt now clears what the previous attempt left on the shared row
190
+ (`exception`, `traceback`, `finished_at`, `runtime_seconds`, the four
191
+ `progress_*` columns). A task that failed once and then succeeded used to
192
+ render as `succeeded` with a stale traceback and a stale progress bar.
193
+ - `POST /api/tasks/{id}/retry` returns `404` for an unknown task id instead of
194
+ an unhandled `ValueError` (a `500`); `retry-bulk` does the same.
195
+ `control.TaskRunNotFound` is a `ValueError` subclass, so Python callers
196
+ already catching `ValueError` are unaffected.
197
+
198
+ ### Fixed
199
+
200
+ - The events listener no longer downgrades a decorator-owned `skipped` status.
201
+ A skipped occurrence returns normally, so the worker emits an ordinary
202
+ `task-succeeded` for the same uuid — which used to flip the row back to
203
+ `succeeded`, making a paused occurrence look as if it had run.
204
+ - `EventBus.publish()` is thread-safe: subscribers are stored with the event
205
+ loop that owns their queue, and publishes are handed back to that loop via
206
+ `call_soon_threadsafe`.
207
+ - The decorator upserts `task_runs` instead of inserting, so it can't raise
208
+ `IntegrityError` when the events listener has already written a row for the
209
+ same uuid.
210
+ - Schedule overrides are matched by an `eta_tolerance_seconds` window rather
211
+ than exact timestamp equality, and each override is claimed by at most one
212
+ occurrence — so a paused occurrence keeps showing as paused, and can't show
213
+ as paused twice on a tightly-spaced schedule.
214
+ - Task timestamps are stamped back to UTC at the API boundary. SQLite drops
215
+ `tzinfo` on a `DateTime(timezone=True)` round-trip, so they used to reach
216
+ JSON unmarked and be parsed as *local* time by `new Date(...)`. The
217
+ `date_from`/`date_to` query parameters are normalized the same way on the
218
+ way in — an offset-carrying bound used to be compared by its wall-clock
219
+ fields, shifting the window by the caller's offset. `GET /api/audit`'s
220
+ `created_at` is stamped the same way.
221
+ - `GET /api/audit` clamps `limit` into `1..500`. SQLite reads `LIMIT -1` as
222
+ *no limit*, so `?limit=-1` returned the entire audit log in one response.
223
+ - The decorator no longer fails a task whose args it cannot JSON-encode. It
224
+ serializes args/kwargs *before* running the body, and a `datetime` kwarg —
225
+ supported by Celery's own serializer, rejected by stdlib `json.dumps` —
226
+ used to raise out of the wrapper and mark the task FAILURE without ever
227
+ running it. Unencodable leaves are now stored as their `repr`.
228
+ - The task list and the task detail page can no longer quote different
229
+ percentages for the same run: the rounding rule (a running task stops at
230
+ 99% until it is genuinely complete) now lives in one shared derivation.
231
+ - A non-JSON SSE frame — a proxy's keep-alive comment, a truncated write — no
232
+ longer throws an uncaught error out of the event handler.
233
+ - The task detail page no longer repaints with a stale response when you
234
+ navigate from one live run to another while a fetch is in flight.
235
+ - The audit log and both schedule screens show the server's own error message
236
+ with a **Try again** button instead of a fixed sentence with nothing to
237
+ click.
238
+ - Retrying a run recorded only by the events listener no longer 500s. The
239
+ listener stores args/kwargs as a Python repr rather than JSON, so
240
+ `json.loads` failed for every undecorated task; the retry path now tries
241
+ JSON, then `ast.literal_eval`, and refuses rather than guessing if neither
242
+ works.
243
+ - `celery-manager events --celery-app module:app` now imports the app from the
244
+ current directory (console scripts don't put it on `sys.path`).
245
+ - SSE stream sends a `: keepalive` comment every 15s when idle so proxies don't
246
+ close it.
247
+
248
+ ### Security
249
+
250
+ - **`create_app()` refuses to start with an empty or default
251
+ (`"change-me"`) `secret_key`**, raising `ValueError`. The default is
252
+ public, so a dashboard running on it accepted a session cookie anyone
253
+ could sign for any username. Workers and the events listener, which never
254
+ sign sessions, are unaffected. Generate a key with
255
+ `python -c "import secrets; print(secrets.token_urlsafe(32))"`.
256
+ - **`GET /api/stream` now requires a session**, like every other non-login
257
+ endpoint. It used to stream every task id and lifecycle event to anyone.
258
+ - **Login no longer reveals whether a username exists.** A missing user now
259
+ pays for a bcrypt check against a dummy hash, so both cases take the same
260
+ time (was ~0.2 s vs ~2 ms). A password over bcrypt's 72-byte limit is a
261
+ `401` for every username instead of a `500` for existing ones only.
262
+ - `create_user()` refuses an empty password (previously accepted) and one
263
+ over 72 bytes UTF-8-encoded with a clear `ValueError` — rather than
264
+ bcrypt's own error on bcrypt ≥ 5, or silent truncation to 72 bytes on
265
+ older bcrypt.
266
+ - A session cookie for a user that has since been deleted is rejected with
267
+ a `401`; it used to keep working until it expired.
268
+ - **Breaking for anyone on a dev SHA: the session cookie is renamed from
269
+ `session` to `cm_session`**, so it no longer collides with Flask's or
270
+ Starlette's own `session` cookie when celery-manager is mounted inside
271
+ another app. Its `Path` is now the app's mount point (`root_path`) rather
272
+ than always `/`. Existing sessions are not carried over — everyone signs in
273
+ once more, and the stale `session` cookie simply expires.
274
+ - Logout deletes the cookie with the same `Path`, `HttpOnly`, `SameSite=lax`
275
+ and `Secure` attributes it was set with.
276
+ - Cross-origin `POST`/`PUT`/`PATCH`/`DELETE` requests are refused with a
277
+ `403` when their `Origin` doesn't match `Host` (or `X-Forwarded-Host`).
278
+ Requests without `Origin` — curl, scripts — are unaffected. A proxy that
279
+ rewrites `Host` must forward the original as `X-Forwarded-Host`.
280
+ - Every response carries `X-Frame-Options: DENY` and
281
+ `Content-Security-Policy: frame-ancestors 'none'` against clickjacking.
282
+ - The `celery-manager events` CLI no longer accepts `--secret-key`, which it
283
+ never used. Drop it from any command line that passes it.
284
+ - New [Security model](docs/configuration.md#security-model) section in the
285
+ configuration docs: every account is a full administrator, there is no
286
+ login rate limiting, logout does not revoke a stolen cookie, and
287
+ `brand_logo_url` may point at an external host.
288
+
289
+ ### Known limitations
290
+
291
+ - **SSE only carries web-process events.** `EventBus` is in-memory in the API
292
+ process, so with the documented deployment (events listener as a separate
293
+ process) `/api/stream` never delivers the listener's task events. The
294
+ dashboard's 5-second poll keeps it current. Don't build on `/api/stream` yet.
295
+ - **No schema migrations.** `init_db()` is `create_all` only; it creates
296
+ missing tables and never alters existing ones.
297
+ - **No role system.** Any authenticated user can perform any control action,
298
+ including dispatching any task name with any arguments.
299
+ - **No login rate limiting.** Rate-limit `POST /api/auth/login` at your
300
+ reverse proxy.
301
+ - **Sessions can't be revoked individually.** They are stateless signed
302
+ tokens valid for 7 days; rotating `secret_key` revokes all of them.
303
+ - `GET /api/settings` is unauthenticated by design (the login screen needs
304
+ the branding).
305
+ - `POST /api/tasks/retry-bulk` is not partial-failure aware: it stops at the
306
+ first unknown id and reports a `404` for the whole batch, after having
307
+ already dispatched the ids ahead of it.
308
+ - `task_runs.eta` is served by the detail endpoint but never written by
309
+ anything, so it is always `null`.
310
+ - Revoked runs have no `finished_at` (only `task-succeeded`/`task-failed` set it).
311
+ - Dispatching an unregistered task name records `task_name` as `"unknown"` when
312
+ the listener creates the row.
313
+ - The events listener needs `worker_send_task_events` (or `-E`) and
314
+ `task_send_sent_event` to be set; without them `worker`/`queue` are null. See
315
+ [Required Celery settings](docs/quickstart.md#required-celery-settings).
316
+ - **macOS prefork workers fail every task** (`ValueError: not enough values to
317
+ unpack`). Upstream Celery 5.6 / billiard 4.3 bug, not celery-manager. Start
318
+ the worker with `FORKED_BY_MULTIPROCESSING=1`, or use `--pool=threads` /
319
+ `--pool=solo` for local dev. See the [quickstart](docs/quickstart.md#required-celery-settings).
320
+ - Under Flask (a2wsgi) the SSE stream leaks a subscriber per disconnect, so
321
+ the Flask demo disables `/api/stream` and the dashboard falls back to
322
+ polling. Run the dashboard as its own uvicorn process for live push.
323
+
324
+ [0.1.0a2]: https://github.com/Pythondeveloper6/celery-manager/releases/tag/v0.1.0a2
325
+ [0.1.0a1]: https://github.com/Pythondeveloper6/celery-manager/releases/tag/v0.1.0a1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pythondeveloper6
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,225 @@
1
+ Metadata-Version: 2.5
2
+ Name: celery-manager
3
+ Version: 0.1.0a2
4
+ Summary: A dashboard and control API for Celery background tasks.
5
+ Project-URL: Homepage, https://github.com/Pythondeveloper6/celery-manager
6
+ Project-URL: Documentation, https://github.com/Pythondeveloper6/celery-manager/tree/main/docs
7
+ Project-URL: Repository, https://github.com/Pythondeveloper6/celery-manager
8
+ Project-URL: Issues, https://github.com/Pythondeveloper6/celery-manager/issues
9
+ Project-URL: Changelog, https://github.com/Pythondeveloper6/celery-manager/blob/main/CHANGELOG.md
10
+ Author-email: pythondeveloper6 <pythondeveloper6@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: celery,dashboard,monitoring,scheduler,task-queue
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Framework :: Celery
17
+ Classifier: Framework :: FastAPI
18
+ Classifier: Framework :: Flask
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: Intended Audience :: System Administrators
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Operating System :: OS Independent
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.10
26
+ Classifier: Programming Language :: Python :: 3.11
27
+ Classifier: Programming Language :: Python :: 3.12
28
+ Classifier: Programming Language :: Python :: 3.13
29
+ Classifier: Topic :: System :: Distributed Computing
30
+ Classifier: Topic :: System :: Monitoring
31
+ Requires-Python: >=3.10
32
+ Requires-Dist: bcrypt>=4.1
33
+ Requires-Dist: celery<6.0,>=5.3
34
+ Requires-Dist: fastapi<1.0,>=0.110
35
+ Requires-Dist: itsdangerous>=2.1
36
+ Requires-Dist: pydantic<3.0,>=2.0
37
+ Requires-Dist: sqlalchemy<3.0,>=2.0
38
+ Provides-Extra: dev
39
+ Requires-Dist: a2wsgi>=1.10; extra == 'dev'
40
+ Requires-Dist: flask>=3.0; extra == 'dev'
41
+ Requires-Dist: httpx>=0.27; extra == 'dev'
42
+ Requires-Dist: pytest>=8.0; extra == 'dev'
43
+ Requires-Dist: ruff<0.18,>=0.17; extra == 'dev'
44
+ Requires-Dist: uvicorn>=0.29; extra == 'dev'
45
+ Provides-Extra: docs
46
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
47
+ Provides-Extra: flask
48
+ Requires-Dist: a2wsgi>=1.10; extra == 'flask'
49
+ Requires-Dist: flask>=3.0; extra == 'flask'
50
+ Provides-Extra: standalone
51
+ Requires-Dist: uvicorn>=0.29; extra == 'standalone'
52
+ Description-Content-Type: text/markdown
53
+
54
+ <div align="center">
55
+
56
+ <h1>
57
+ <picture>
58
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Pythondeveloper6/celery-manager/main/docs/assets/logo-wordmark-dark.svg">
59
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Pythondeveloper6/celery-manager/main/docs/assets/logo-wordmark-light.svg">
60
+ <img alt="celery-manager" src="https://raw.githubusercontent.com/Pythondeveloper6/celery-manager/main/docs/assets/logo-wordmark-light.svg" height="56">
61
+ </picture>
62
+ </h1>
63
+
64
+ A dashboard and control API for Celery background tasks — for any
65
+ Django, Flask, or FastAPI project that already uses Celery.
66
+
67
+ [![CI](https://github.com/Pythondeveloper6/celery-manager/actions/workflows/ci.yml/badge.svg)](https://github.com/Pythondeveloper6/celery-manager/actions/workflows/ci.yml)
68
+ ![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%E2%80%933.13-3776AB?logo=python&logoColor=white)
69
+ ![License: MIT](https://img.shields.io/badge/license-MIT-green)
70
+ ![Status: alpha](https://img.shields.io/badge/status-alpha-orange)
71
+
72
+ </div>
73
+
74
+ Point it at your existing Celery app and you get a web UI and a REST API to
75
+ see what your tasks are doing, act on them, and — the feature this project
76
+ exists for — **pause or cancel a single future occurrence of a periodic task**
77
+ without touching your beat schedule or restarting anything.
78
+
79
+ ## What you get
80
+
81
+ - **Task visibility** — every task run (decorated or not) with status, args,
82
+ worker, queue, timings, exceptions, and full tracebacks.
83
+ - **Full run history** — browse past runs beyond the newest page, search by
84
+ task name, task ID, or args/kwargs content, and filter by status and date
85
+ range.
86
+ - **Live progress** — call `celery_manager.report_progress(current, total,
87
+ message)` inside a task body and watch the bar move, with elapsed time and
88
+ a projected finish while the task is still running.
89
+ - **Log capture** — `logging` output from inside a task body (and `print()`
90
+ too, in a real worker — [see why](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/decorator.md#what-log-capture-actually-captures)),
91
+ attached to that run and readable in the dashboard.
92
+ - **Control** — start, cancel/terminate, retry, and bulk retry, from the UI
93
+ or the API. Retries keep their lineage, so you can walk the chain.
94
+ - **Per-occurrence schedule control** — skip or cancel one upcoming run of a
95
+ periodic task, and see it stay paused.
96
+ - **Audit log** — who did what, when, for every control action.
97
+ - **Light and dark themes** — an explicit per-user choice, not just whatever
98
+ the OS says.
99
+ - **Brandable** — set the product name, accent colour, and logo from a
100
+ settings screen; it applies for everyone on the deployment.
101
+ - **Built-in auth** — bcrypt-hashed users and signed session cookies.
102
+ - **No Node toolchain required** — the React dashboard ships prebuilt inside
103
+ the Python package.
104
+
105
+ ## Install
106
+
107
+ celery-manager is an alpha, so pass `--pre` to pip:
108
+
109
+ ```bash
110
+ pip install --pre celery-manager
111
+ ```
112
+
113
+ With extras:
114
+
115
+ ```bash
116
+ pip install --pre "celery-manager[standalone]" # adds uvicorn
117
+ pip install --pre "celery-manager[flask]" # adds Flask and a2wsgi
118
+ ```
119
+
120
+ Alternatively, install a tagged release straight from git:
121
+
122
+ ```bash
123
+ pip install "git+https://github.com/Pythondeveloper6/celery-manager.git@v0.1.0a2"
124
+ ```
125
+
126
+ (Extras with git: `"celery-manager[standalone] @
127
+ git+https://github.com/Pythondeveloper6/celery-manager.git@v0.1.0a2"`.)
128
+
129
+ Optional extras:
130
+
131
+ - `standalone` — pulls in uvicorn, to run the dashboard on its own rather than
132
+ mounting it into an ASGI app you already serve.
133
+ - `flask` — pulls in Flask and `a2wsgi`, to mount the dashboard in a Flask
134
+ (WSGI) app; see [Integrating with your app](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/integrations.md#flask-wsgi).
135
+
136
+ ## Wire it up
137
+
138
+ celery-manager runs in two places, and **both** need configuring — this is
139
+ the step most easily missed:
140
+
141
+ ```python
142
+ from celery_manager.config import CeleryManagerConfig
143
+
144
+ config = CeleryManagerConfig(
145
+ celery_app=your_celery_app,
146
+ db_url="sqlite:///celery_manager.db", # any SQLAlchemy URL
147
+ secret_key="a-real-secret-in-production",
148
+ )
149
+ ```
150
+
151
+ **1. The dashboard process** — build the ASGI app and serve or mount it:
152
+
153
+ ```python
154
+ from celery_manager.api.app import create_app
155
+
156
+ app, _ = create_app(config) # then: uvicorn your_module:app
157
+ ```
158
+
159
+ **2. The Celery worker process** — where `@track`-decorated tasks actually
160
+ run:
161
+
162
+ ```python
163
+ import celery_manager
164
+
165
+ celery_manager.setup(config) # at import time, in your tasks module
166
+
167
+ @app.task(bind=True)
168
+ @celery_manager.track(schedule_name="send-report-every-2-hours")
169
+ def send_report(self):
170
+ ...
171
+ ```
172
+
173
+ Then run the events listener alongside your worker and beat, so the dashboard
174
+ sees every task rather than only the decorated ones:
175
+
176
+ ```bash
177
+ celery-manager events --celery-app your_module:your_celery_app \
178
+ --db-url sqlite:///celery_manager.db
179
+ ```
180
+
181
+ The listener sees nothing unless workers emit events: set `worker_send_task_events = True`
182
+ and `task_send_sent_event = True` (or run the worker with `-E`). See
183
+ [Required Celery settings](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/quickstart.md#required-celery-settings).
184
+
185
+ **Two things that stop people on a first run**, both covered in the
186
+ [Quickstart](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/quickstart.md): there is no default account, so you have to
187
+ create one (`celery_manager.auth.users.create_user`); and on plain HTTP you
188
+ need `cookie_secure=False`, or you will log in successfully and then
189
+ immediately appear logged out.
190
+
191
+ ## Documentation
192
+
193
+ Full documentation lives in [`docs/`](https://github.com/Pythondeveloper6/celery-manager/tree/main/docs/) (an MkDocs Material site — run
194
+ `mkdocs serve` after `pip install -e ".[docs]"`):
195
+
196
+ - [Quickstart](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/quickstart.md) — install through first sign-in.
197
+ - [The `@track` decorator](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/decorator.md) — wiring, skip/cancel, logs.
198
+ - [Live progress](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/progress.md) — reporting progress from inside a task.
199
+ - [REST API](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/rest-api.md) — every endpoint and payload.
200
+ - [Dashboard](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/dashboard.md) — the screens and what they do.
201
+ - [Configuration](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/configuration.md) — every config field, the schema
202
+ and upgrade story, and production notes.
203
+ - [Working on celery-manager](https://github.com/Pythondeveloper6/celery-manager/blob/main/docs/contributing.md) — running the tests,
204
+ building the docs, and rebuilding the committed dashboard bundle.
205
+
206
+ Changes between versions — including what breaks for development builds —
207
+ are in [`CHANGELOG.md`](https://github.com/Pythondeveloper6/celery-manager/blob/main/CHANGELOG.md).
208
+
209
+ A complete runnable Django example is in
210
+ [`examples/django_demo/`](https://github.com/Pythondeveloper6/celery-manager/tree/main/examples/django_demo/).
211
+
212
+ ## Requirements
213
+
214
+ Python 3.10+, Celery 5.x. Storage defaults to SQLite with zero configuration
215
+ and swaps to Postgres/MySQL/anything SQLAlchemy supports via `db_url`.
216
+
217
+ ## Status
218
+
219
+ This is the first alpha, **0.1.0a2**. It is feature-complete for the v1 scope,
220
+ but APIs may change before 0.1.0. See [`plan.md`](https://github.com/Pythondeveloper6/celery-manager/blob/main/plan.md) for the implementation history and the
221
+ backlog of known gaps and planned follow-ups.
222
+
223
+ ## License
224
+
225
+ MIT — see [`LICENSE`](https://github.com/Pythondeveloper6/celery-manager/blob/main/LICENSE).