django-nativemojo 0.1.10__py3-none-any.whl → 0.1.16__py3-none-any.whl

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 (276) hide show
  1. django_nativemojo-0.1.16.dist-info/METADATA +138 -0
  2. django_nativemojo-0.1.16.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/__init__.py +5 -0
  5. mojo/apps/account/management/commands/__init__.py +6 -0
  6. mojo/apps/account/management/commands/serializer_admin.py +651 -0
  7. mojo/apps/account/migrations/0004_user_avatar.py +20 -0
  8. mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
  9. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  10. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  11. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  12. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  13. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  14. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  15. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  16. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  17. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  18. mojo/apps/account/models/__init__.py +2 -0
  19. mojo/apps/account/models/device.py +281 -0
  20. mojo/apps/account/models/group.py +319 -15
  21. mojo/apps/account/models/member.py +29 -5
  22. mojo/apps/account/models/push/__init__.py +4 -0
  23. mojo/apps/account/models/push/config.py +112 -0
  24. mojo/apps/account/models/push/delivery.py +93 -0
  25. mojo/apps/account/models/push/device.py +66 -0
  26. mojo/apps/account/models/push/template.py +99 -0
  27. mojo/apps/account/models/user.py +369 -19
  28. mojo/apps/account/rest/__init__.py +2 -0
  29. mojo/apps/account/rest/device.py +39 -0
  30. mojo/apps/account/rest/group.py +9 -0
  31. mojo/apps/account/rest/push.py +187 -0
  32. mojo/apps/account/rest/user.py +100 -6
  33. mojo/apps/account/services/__init__.py +1 -0
  34. mojo/apps/account/services/push.py +363 -0
  35. mojo/apps/aws/migrations/0001_initial.py +206 -0
  36. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  37. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  38. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  39. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  40. mojo/apps/aws/models/__init__.py +19 -0
  41. mojo/apps/aws/models/email_attachment.py +99 -0
  42. mojo/apps/aws/models/email_domain.py +218 -0
  43. mojo/apps/aws/models/email_template.py +132 -0
  44. mojo/apps/aws/models/incoming_email.py +197 -0
  45. mojo/apps/aws/models/mailbox.py +288 -0
  46. mojo/apps/aws/models/sent_message.py +175 -0
  47. mojo/apps/aws/rest/__init__.py +7 -0
  48. mojo/apps/aws/rest/email.py +33 -0
  49. mojo/apps/aws/rest/email_ops.py +183 -0
  50. mojo/apps/aws/rest/messages.py +32 -0
  51. mojo/apps/aws/rest/s3.py +64 -0
  52. mojo/apps/aws/rest/send.py +101 -0
  53. mojo/apps/aws/rest/sns.py +403 -0
  54. mojo/apps/aws/rest/templates.py +19 -0
  55. mojo/apps/aws/services/__init__.py +32 -0
  56. mojo/apps/aws/services/email.py +390 -0
  57. mojo/apps/aws/services/email_ops.py +548 -0
  58. mojo/apps/docit/__init__.py +6 -0
  59. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  60. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  61. mojo/apps/docit/migrations/0001_initial.py +113 -0
  62. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  63. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  64. mojo/apps/docit/models/__init__.py +17 -0
  65. mojo/apps/docit/models/asset.py +231 -0
  66. mojo/apps/docit/models/book.py +227 -0
  67. mojo/apps/docit/models/page.py +319 -0
  68. mojo/apps/docit/models/page_revision.py +203 -0
  69. mojo/apps/docit/rest/__init__.py +10 -0
  70. mojo/apps/docit/rest/asset.py +17 -0
  71. mojo/apps/docit/rest/book.py +22 -0
  72. mojo/apps/docit/rest/page.py +22 -0
  73. mojo/apps/docit/rest/page_revision.py +17 -0
  74. mojo/apps/docit/services/__init__.py +11 -0
  75. mojo/apps/docit/services/docit.py +315 -0
  76. mojo/apps/docit/services/markdown.py +44 -0
  77. mojo/apps/fileman/README.md +8 -8
  78. mojo/apps/fileman/backends/base.py +76 -70
  79. mojo/apps/fileman/backends/filesystem.py +86 -86
  80. mojo/apps/fileman/backends/s3.py +409 -108
  81. mojo/apps/fileman/migrations/0001_initial.py +106 -0
  82. mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
  83. mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
  84. mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
  85. mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
  86. mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
  87. mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
  88. mojo/apps/fileman/migrations/0008_file_category.py +18 -0
  89. mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
  90. mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
  91. mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
  92. mojo/apps/fileman/models/__init__.py +1 -5
  93. mojo/apps/fileman/models/file.py +240 -58
  94. mojo/apps/fileman/models/manager.py +427 -31
  95. mojo/apps/fileman/models/rendition.py +118 -0
  96. mojo/apps/fileman/renderer/__init__.py +111 -0
  97. mojo/apps/fileman/renderer/audio.py +403 -0
  98. mojo/apps/fileman/renderer/base.py +205 -0
  99. mojo/apps/fileman/renderer/document.py +404 -0
  100. mojo/apps/fileman/renderer/image.py +222 -0
  101. mojo/apps/fileman/renderer/utils.py +297 -0
  102. mojo/apps/fileman/renderer/video.py +304 -0
  103. mojo/apps/fileman/rest/__init__.py +1 -18
  104. mojo/apps/fileman/rest/upload.py +22 -32
  105. mojo/apps/fileman/signals.py +58 -0
  106. mojo/apps/fileman/tasks.py +254 -0
  107. mojo/apps/fileman/utils/__init__.py +40 -16
  108. mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
  109. mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
  110. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  111. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  112. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  113. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  114. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  115. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  116. mojo/apps/incident/models/__init__.py +2 -0
  117. mojo/apps/incident/models/event.py +35 -0
  118. mojo/apps/incident/models/history.py +36 -0
  119. mojo/apps/incident/models/incident.py +3 -1
  120. mojo/apps/incident/models/ticket.py +62 -0
  121. mojo/apps/incident/reporter.py +21 -1
  122. mojo/apps/incident/rest/__init__.py +1 -0
  123. mojo/apps/incident/rest/event.py +7 -1
  124. mojo/apps/incident/rest/ticket.py +43 -0
  125. mojo/apps/jobs/__init__.py +489 -0
  126. mojo/apps/jobs/adapters.py +24 -0
  127. mojo/apps/jobs/cli.py +616 -0
  128. mojo/apps/jobs/daemon.py +370 -0
  129. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  130. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  131. mojo/apps/jobs/handlers/__init__.py +5 -0
  132. mojo/apps/jobs/handlers/webhook.py +317 -0
  133. mojo/apps/jobs/job_engine.py +734 -0
  134. mojo/apps/jobs/keys.py +203 -0
  135. mojo/apps/jobs/local_queue.py +363 -0
  136. mojo/apps/jobs/management/__init__.py +3 -0
  137. mojo/apps/jobs/management/commands/__init__.py +3 -0
  138. mojo/apps/jobs/manager.py +1327 -0
  139. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  140. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  141. mojo/apps/jobs/models/__init__.py +6 -0
  142. mojo/apps/jobs/models/job.py +441 -0
  143. mojo/apps/jobs/rest/__init__.py +2 -0
  144. mojo/apps/jobs/rest/control.py +466 -0
  145. mojo/apps/jobs/rest/jobs.py +421 -0
  146. mojo/apps/jobs/scheduler.py +571 -0
  147. mojo/apps/jobs/services/__init__.py +6 -0
  148. mojo/apps/jobs/services/job_actions.py +465 -0
  149. mojo/apps/jobs/settings.py +209 -0
  150. mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
  151. mojo/apps/logit/models/log.py +7 -1
  152. mojo/apps/metrics/__init__.py +8 -1
  153. mojo/apps/metrics/redis_metrics.py +198 -0
  154. mojo/apps/metrics/rest/__init__.py +3 -0
  155. mojo/apps/metrics/rest/categories.py +266 -0
  156. mojo/apps/metrics/rest/helpers.py +48 -0
  157. mojo/apps/metrics/rest/permissions.py +99 -0
  158. mojo/apps/metrics/rest/values.py +277 -0
  159. mojo/apps/metrics/utils.py +19 -2
  160. mojo/decorators/auth.py +6 -1
  161. mojo/decorators/http.py +47 -3
  162. mojo/helpers/aws/__init__.py +45 -0
  163. mojo/helpers/aws/ec2.py +804 -0
  164. mojo/helpers/aws/iam.py +748 -0
  165. mojo/helpers/aws/inbound_email.py +309 -0
  166. mojo/helpers/aws/kms.py +413 -0
  167. mojo/helpers/aws/s3.py +451 -11
  168. mojo/helpers/aws/ses.py +483 -0
  169. mojo/helpers/aws/ses_domain.py +959 -0
  170. mojo/helpers/aws/sns.py +461 -0
  171. mojo/helpers/crypto/__init__.py +1 -1
  172. mojo/helpers/crypto/utils.py +15 -0
  173. mojo/helpers/dates.py +18 -0
  174. mojo/helpers/location/__init__.py +2 -0
  175. mojo/helpers/location/countries.py +262 -0
  176. mojo/helpers/location/geolocation.py +196 -0
  177. mojo/helpers/logit.py +37 -0
  178. mojo/helpers/redis/__init__.py +2 -0
  179. mojo/helpers/redis/adapter.py +606 -0
  180. mojo/helpers/redis/client.py +48 -0
  181. mojo/helpers/redis/pool.py +225 -0
  182. mojo/helpers/request.py +8 -0
  183. mojo/helpers/response.py +14 -2
  184. mojo/helpers/settings/__init__.py +2 -0
  185. mojo/helpers/{settings.py → settings/helper.py} +1 -37
  186. mojo/helpers/settings/parser.py +132 -0
  187. mojo/middleware/auth.py +1 -1
  188. mojo/middleware/cors.py +40 -0
  189. mojo/middleware/logging.py +131 -12
  190. mojo/middleware/mojo.py +10 -0
  191. mojo/models/rest.py +494 -65
  192. mojo/models/secrets.py +98 -3
  193. mojo/serializers/__init__.py +106 -0
  194. mojo/serializers/core/__init__.py +90 -0
  195. mojo/serializers/core/cache/__init__.py +121 -0
  196. mojo/serializers/core/cache/backends.py +518 -0
  197. mojo/serializers/core/cache/base.py +102 -0
  198. mojo/serializers/core/cache/disabled.py +181 -0
  199. mojo/serializers/core/cache/memory.py +287 -0
  200. mojo/serializers/core/cache/redis.py +533 -0
  201. mojo/serializers/core/cache/utils.py +454 -0
  202. mojo/serializers/core/manager.py +550 -0
  203. mojo/serializers/core/serializer.py +475 -0
  204. mojo/serializers/examples/settings.py +322 -0
  205. mojo/serializers/formats/csv.py +393 -0
  206. mojo/serializers/formats/localizers.py +509 -0
  207. mojo/serializers/{models.py → simple.py} +38 -15
  208. mojo/serializers/suggested_improvements.md +388 -0
  209. testit/client.py +1 -1
  210. testit/helpers.py +35 -4
  211. testit/runner.py +23 -6
  212. django_nativemojo-0.1.10.dist-info/METADATA +0 -96
  213. django_nativemojo-0.1.10.dist-info/RECORD +0 -194
  214. mojo/apps/metrics/rest/db.py +0 -0
  215. mojo/apps/notify/README.md +0 -91
  216. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  217. mojo/apps/notify/admin.py +0 -52
  218. mojo/apps/notify/handlers/example_handlers.py +0 -516
  219. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  220. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  221. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  222. mojo/apps/notify/handlers/ses/message.py +0 -86
  223. mojo/apps/notify/management/commands/__init__.py +0 -1
  224. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  225. mojo/apps/notify/mod +0 -0
  226. mojo/apps/notify/models/__init__.py +0 -12
  227. mojo/apps/notify/models/account.py +0 -128
  228. mojo/apps/notify/models/attachment.py +0 -24
  229. mojo/apps/notify/models/bounce.py +0 -68
  230. mojo/apps/notify/models/complaint.py +0 -40
  231. mojo/apps/notify/models/inbox.py +0 -113
  232. mojo/apps/notify/models/inbox_message.py +0 -173
  233. mojo/apps/notify/models/outbox.py +0 -129
  234. mojo/apps/notify/models/outbox_message.py +0 -288
  235. mojo/apps/notify/models/template.py +0 -30
  236. mojo/apps/notify/providers/aws.py +0 -73
  237. mojo/apps/notify/rest/ses.py +0 -0
  238. mojo/apps/notify/utils/__init__.py +0 -2
  239. mojo/apps/notify/utils/notifications.py +0 -404
  240. mojo/apps/notify/utils/parsing.py +0 -202
  241. mojo/apps/notify/utils/render.py +0 -144
  242. mojo/apps/tasks/README.md +0 -118
  243. mojo/apps/tasks/__init__.py +0 -11
  244. mojo/apps/tasks/manager.py +0 -489
  245. mojo/apps/tasks/rest/__init__.py +0 -2
  246. mojo/apps/tasks/rest/hooks.py +0 -0
  247. mojo/apps/tasks/rest/tasks.py +0 -62
  248. mojo/apps/tasks/runner.py +0 -174
  249. mojo/apps/tasks/tq_handlers.py +0 -14
  250. mojo/helpers/aws/setup_email.py +0 -0
  251. mojo/helpers/redis.py +0 -10
  252. mojo/models/meta.py +0 -262
  253. mojo/ws4redis/README.md +0 -174
  254. mojo/ws4redis/__init__.py +0 -2
  255. mojo/ws4redis/client.py +0 -283
  256. mojo/ws4redis/connection.py +0 -327
  257. mojo/ws4redis/exceptions.py +0 -32
  258. mojo/ws4redis/redis.py +0 -183
  259. mojo/ws4redis/servers/base.py +0 -86
  260. mojo/ws4redis/servers/django.py +0 -171
  261. mojo/ws4redis/servers/uwsgi.py +0 -63
  262. mojo/ws4redis/settings.py +0 -45
  263. mojo/ws4redis/utf8validator.py +0 -128
  264. mojo/ws4redis/websocket.py +0 -403
  265. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
  266. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
  267. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
  268. /mojo/apps/{notify → aws}/__init__.py +0 -0
  269. /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
  270. /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
  271. /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
  272. /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
  273. /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
  274. /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
  275. /mojo/{serializers → rest}/openapi.py +0 -0
  276. /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
@@ -0,0 +1,421 @@
1
+ from mojo import decorators as md
2
+ from mojo.helpers.response import JsonResponse
3
+ from mojo.helpers import logit
4
+ from mojo.helpers.settings import settings
5
+ from mojo.apps.jobs.models import Job, JobEvent, JobLog
6
+ from mojo.apps.jobs.manager import get_manager
7
+ from mojo.apps.jobs import publish, cancel, status
8
+ from django.utils import timezone
9
+ from django.db.models import Q
10
+ import json
11
+
12
+
13
+ # Basic CRUD for Jobs (with RestMeta permissions)
14
+ @md.URL('job')
15
+ @md.URL('job/<str:pk>')
16
+ def on_job(request, pk=None):
17
+ """Standard CRUD operations for jobs with automatic permission handling."""
18
+
19
+ return Job.on_rest_request(request, pk)
20
+
21
+
22
+ # Basic CRUD for Job Events
23
+ @md.URL('event')
24
+ @md.URL('event/<int:pk>')
25
+ def on_job_event(request, pk=None):
26
+ """Standard CRUD operations for job events."""
27
+ return JobEvent.on_rest_request(request, pk)
28
+
29
+
30
+ # Basic CRUD for Job Logs
31
+ @md.URL('logs')
32
+ @md.URL('logs/<int:pk>')
33
+ def on_job_logs(request, pk=None):
34
+ """Standard CRUD operations for job logs."""
35
+ return JobLog.on_rest_request(request, pk)
36
+
37
+
38
+ # Get job status
39
+ @md.GET('status/<str:job_id>')
40
+ @md.requires_perms('manage_jobs', 'view_jobs')
41
+ def on_get_job_status(request, job_id):
42
+ """Get the current status of a job."""
43
+ try:
44
+ job_status = status(job_id)
45
+
46
+ if job_status is None:
47
+ return JsonResponse({
48
+ 'status': False,
49
+ 'error': 'Job not found'
50
+ }, status=404)
51
+
52
+ return JsonResponse({
53
+ 'status': True,
54
+ 'data': job_status
55
+ })
56
+
57
+ except Exception as e:
58
+ return JsonResponse({
59
+ 'status': False,
60
+ 'error': str(e)
61
+ }, status=400)
62
+
63
+
64
+ # Cancel a job
65
+ @md.POST('cancel')
66
+ @md.requires_perms('manage_jobs')
67
+ @md.requires_params('job_id')
68
+ def on_cancel_job(request):
69
+ """Request cancellation of a job."""
70
+ try:
71
+ job_id = request.DATA['job_id']
72
+ result = cancel(job_id)
73
+
74
+ return JsonResponse({
75
+ 'status': result,
76
+ 'message': f'Job {job_id} cancellation {"requested" if result else "failed"}'
77
+ })
78
+
79
+ except Exception as e:
80
+ return JsonResponse({
81
+ 'status': False,
82
+ 'error': str(e)
83
+ }, status=400)
84
+
85
+
86
+ # Retry a job
87
+ @md.POST('retry')
88
+ @md.requires_perms('manage_jobs')
89
+ @md.requires_params('job_id')
90
+ def on_retry_job(request):
91
+ """Retry a failed or canceled job."""
92
+ try:
93
+ job_id = request.DATA['job_id']
94
+ delay = request.DATA.get('delay')
95
+
96
+ # Get the job
97
+ try:
98
+ job = Job.objects.get(id=job_id)
99
+ except Job.DoesNotExist:
100
+ return JsonResponse({
101
+ 'status': False,
102
+ 'error': 'Job not found'
103
+ }, status=404)
104
+
105
+ # Use the service to retry
106
+ from mojo.apps.jobs.services import JobActionsService
107
+ result = JobActionsService.retry_job(job, delay=delay)
108
+
109
+ return JsonResponse(result)
110
+
111
+ except Exception as e:
112
+ return JsonResponse({
113
+ 'status': False,
114
+ 'error': str(e)
115
+ }, status=400)
116
+
117
+
118
+
119
+ # Get channel health
120
+ @md.GET('health/<str:channel>')
121
+ @md.requires_perms('manage_jobs', 'view_jobs')
122
+ def on_channel_health(request, channel):
123
+ """Get comprehensive health metrics for a channel."""
124
+ try:
125
+ manager = get_manager()
126
+ health = manager.get_channel_health(channel)
127
+
128
+ return JsonResponse({
129
+ 'status': True,
130
+ 'data': health
131
+ })
132
+
133
+ except Exception as e:
134
+ return JsonResponse({
135
+ 'status': False,
136
+ 'error': str(e)
137
+ }, status=400)
138
+
139
+
140
+ # Get all channels health
141
+ @md.GET('health')
142
+ @md.requires_perms('manage_jobs', 'view_jobs')
143
+ def on_health_overview(request):
144
+ """Get health overview for all configured channels."""
145
+ try:
146
+ from django.conf import settings
147
+ manager = get_manager()
148
+
149
+ channels = getattr(settings, 'JOBS_CHANNELS', ['default'])
150
+ health_data = {}
151
+
152
+ for channel in channels:
153
+ health_data[channel] = manager.get_channel_health(channel)
154
+
155
+ # Calculate aggregate stats
156
+ total_unclaimed = sum(h['messages']['unclaimed'] for h in health_data.values())
157
+ total_pending = sum(h['messages']['pending'] for h in health_data.values())
158
+ total_stuck = sum(h['messages']['stuck'] for h in health_data.values())
159
+ total_runners = sum(h['runners']['active'] for h in health_data.values())
160
+
161
+ # Determine overall status
162
+ overall_status = 'healthy'
163
+ if any(h['status'] == 'critical' for h in health_data.values()):
164
+ overall_status = 'critical'
165
+ elif any(h['status'] == 'warning' for h in health_data.values()):
166
+ overall_status = 'warning'
167
+
168
+ return JsonResponse({
169
+ 'status': True,
170
+ 'data': {
171
+ 'overall_status': overall_status,
172
+ 'totals': {
173
+ 'unclaimed': total_unclaimed,
174
+ 'pending': total_pending,
175
+ 'stuck': total_stuck,
176
+ 'runners': total_runners
177
+ },
178
+ 'channels': health_data
179
+ }
180
+ })
181
+
182
+ except Exception as e:
183
+ return JsonResponse({
184
+ 'status': False,
185
+ 'error': str(e)
186
+ }, status=400)
187
+
188
+
189
+ # Get active runners
190
+ @md.GET('runners')
191
+ @md.requires_perms('manage_jobs', 'view_jobs')
192
+ def on_list_runners(request):
193
+ """List all active runners with their status."""
194
+ try:
195
+ manager = get_manager()
196
+
197
+ # Optional channel filter
198
+ channel = request.DATA.get('channel')
199
+ runners = manager.get_runners(channel=channel)
200
+
201
+ # Set id field for each runner
202
+ for r in runners:
203
+ r["id"] = r["runner_id"]
204
+
205
+ return JsonResponse({
206
+ 'status': True,
207
+ 'count': len(runners),
208
+ 'data': runners
209
+ })
210
+
211
+ except Exception as e:
212
+ return JsonResponse({
213
+ 'status': False,
214
+ 'error': str(e)
215
+ }, status=400)
216
+
217
+
218
+ # Ping a specific runner
219
+ @md.POST('runners/ping')
220
+ @md.requires_perms('manage_jobs')
221
+ @md.requires_params('runner_id')
222
+ def on_ping_runner(request):
223
+ """Ping a specific runner to check if it's responsive."""
224
+ try:
225
+ manager = get_manager()
226
+ runner_id = request.DATA['runner_id']
227
+ timeout = float(request.DATA.get('timeout', 2.0))
228
+
229
+ result = manager.ping(runner_id, timeout=timeout)
230
+
231
+ return JsonResponse({
232
+ 'status': True,
233
+ 'runner_id': runner_id,
234
+ 'responsive': result
235
+ })
236
+
237
+ except Exception as e:
238
+ return JsonResponse({
239
+ 'status': False,
240
+ 'error': str(e)
241
+ }, status=400)
242
+
243
+
244
+ # Shutdown a runner
245
+ @md.POST('runners/shutdown')
246
+ @md.requires_perms('manage_jobs')
247
+ @md.requires_params('runner_id')
248
+ def on_shutdown_runner(request):
249
+ """Request a runner to shutdown gracefully."""
250
+ try:
251
+ manager = get_manager()
252
+ runner_id = request.DATA['runner_id']
253
+ graceful = request.DATA.get('graceful', True)
254
+
255
+ manager.shutdown(runner_id, graceful=bool(graceful))
256
+
257
+ return JsonResponse({
258
+ 'status': True,
259
+ 'message': f'Shutdown command sent to runner {runner_id}'
260
+ })
261
+
262
+ except Exception as e:
263
+ return JsonResponse({
264
+ 'status': False,
265
+ 'error': str(e)
266
+ }, status=400)
267
+
268
+
269
+ # Broadcast command to all runners
270
+ @md.POST('runners/broadcast')
271
+ @md.requires_perms('manage_jobs')
272
+ @md.requires_params('command')
273
+ def on_broadcast_command(request):
274
+ """Broadcast a command to all runners."""
275
+ try:
276
+ manager = get_manager()
277
+ command = request.DATA['command']
278
+ data = request.DATA.get('data', {})
279
+ timeout = float(request.DATA.get('timeout', 2.0))
280
+
281
+ # Validate command
282
+ valid_commands = ['status', 'shutdown', 'pause', 'resume', 'reload']
283
+ if command not in valid_commands:
284
+ return JsonResponse({
285
+ 'status': False,
286
+ 'error': f'Invalid command. Must be one of: {", ".join(valid_commands)}'
287
+ }, status=400)
288
+
289
+ responses = manager.broadcast_command(command, data=data, timeout=timeout)
290
+
291
+ return JsonResponse({
292
+ 'status': True,
293
+ 'command': command,
294
+ 'responses_count': len(responses),
295
+ 'responses': responses
296
+ })
297
+
298
+ except Exception as e:
299
+ return JsonResponse({
300
+ 'status': False,
301
+ 'error': str(e)
302
+ }, status=400)
303
+
304
+
305
+ # Get system stats
306
+ @md.GET('stats')
307
+ @md.requires_perms('manage_jobs', 'view_jobs')
308
+ def on_system_stats(request):
309
+ """Get overall system statistics."""
310
+ try:
311
+ manager = get_manager()
312
+ stats = manager.get_stats()
313
+
314
+ return JsonResponse({
315
+ 'status': True,
316
+ 'data': stats
317
+ })
318
+
319
+ except Exception as e:
320
+ return JsonResponse({
321
+ 'status': False,
322
+ 'error': str(e)
323
+ }, status=400)
324
+
325
+
326
+
327
+ @md.POST('test')
328
+ @md.requires_perms('manage_jobs')
329
+ def on_system_test(request):
330
+ from mojo.apps import jobs
331
+ jobs.publish(
332
+ "mojo.apps.jobs.examples.sample_jobs.send_email",
333
+ {
334
+ "recipients": ["user@example.com"],
335
+ "subject": "Test Email",
336
+ "body": "This is a test email."
337
+ },
338
+ delay=30
339
+ )
340
+
341
+ jobs.publish(
342
+ "mojo.apps.jobs.examples.sample_jobs.simulate_long_job",
343
+ {
344
+ "delay": 15
345
+ },
346
+ channel='priority'
347
+ )
348
+ return JsonResponse({
349
+ 'status': True,
350
+ 'message': 'Test job should be running.'
351
+ })
352
+
353
+
354
+ @md.POST('tests')
355
+ @md.requires_perms('manage_jobs')
356
+ def on_system_tests(request):
357
+ from mojo.apps import jobs
358
+ import random
359
+
360
+ base_job_list = [
361
+ {
362
+ "func": "mojo.apps.jobs.examples.sample_jobs.send_email",
363
+ "payload": {
364
+ "recipients": ["user@example.com"],
365
+ "subject": "Test Email",
366
+ "body": "This is a test email."
367
+ },
368
+ "channel": 'email'
369
+ },
370
+ {
371
+ "func": "mojo.apps.jobs.examples.sample_jobs.process_file_upload",
372
+ "payload": {
373
+ "file_path": "/path/to/file"
374
+ },
375
+ "channel": 'priority'
376
+ },
377
+ {
378
+ "func": "mojo.apps.jobs.examples.sample_jobs.process_file_upload",
379
+ "payload": {
380
+ "file_error_path": "/path/to/file"
381
+ },
382
+ "channel": 'default'
383
+ }
384
+ ]
385
+
386
+ fetch_job = {
387
+ "func": "mojo.apps.jobs.examples.sample_jobs.fetch_external_api",
388
+ "payload": {
389
+ "url": "https://nativemojo.com/"
390
+ },
391
+ "channel": 'webhooks'
392
+ }
393
+
394
+ job_list = []
395
+ channels = settings.get("JOBS_CHANNELS", ["email"])
396
+ for channel in channels:
397
+ j = random.choice(base_job_list)
398
+ j["channel"] = channel
399
+ job_list.append(j)
400
+
401
+ job_list.append(fetch_job)
402
+ # lets schedule some jobs as well
403
+ for i in range(10):
404
+ j = random.choice(base_job_list)
405
+ j = j.copy()
406
+ j["delay"] = random.randint(30, 300)
407
+ job_list.append(j)
408
+
409
+ for i in range(50):
410
+ j = random.choice(base_job_list)
411
+ job_list.append(j.copy())
412
+
413
+
414
+
415
+
416
+ for jd in job_list:
417
+ jobs.publish(**jd)
418
+ return JsonResponse({
419
+ 'status': True,
420
+ 'message': 'Test job should be running.'
421
+ })