django-cfg 1.4.107__py3-none-any.whl → 1.4.109__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.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (140) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/views/profile.py +19 -9
  3. django_cfg/apps/centrifugo/views/admin_api.py +4 -7
  4. django_cfg/apps/centrifugo/views/monitoring.py +3 -6
  5. django_cfg/apps/centrifugo/views/testing_api.py +3 -6
  6. django_cfg/apps/dashboard/services/system_health_service.py +16 -11
  7. django_cfg/apps/dashboard/views/activity_views.py +3 -5
  8. django_cfg/apps/dashboard/views/apizones_views.py +4 -5
  9. django_cfg/apps/dashboard/views/charts_views.py +4 -5
  10. django_cfg/apps/dashboard/views/overview_views.py +4 -5
  11. django_cfg/apps/dashboard/views/statistics_views.py +4 -5
  12. django_cfg/apps/dashboard/views/system_views.py +4 -5
  13. django_cfg/apps/knowbase/__init__.py +2 -2
  14. django_cfg/apps/knowbase/apps.py +2 -8
  15. django_cfg/apps/knowbase/views/base.py +9 -4
  16. django_cfg/apps/support/views/api.py +16 -7
  17. django_cfg/apps/tasks/__init__.py +61 -2
  18. django_cfg/apps/tasks/admin/__init__.py +3 -10
  19. django_cfg/apps/tasks/admin/config.py +98 -0
  20. django_cfg/apps/tasks/admin/task_log.py +265 -0
  21. django_cfg/apps/tasks/apps.py +7 -9
  22. django_cfg/apps/tasks/filters/__init__.py +10 -0
  23. django_cfg/apps/tasks/filters/task_log.py +121 -0
  24. django_cfg/apps/tasks/migrations/0001_initial.py +196 -0
  25. django_cfg/apps/tasks/models/__init__.py +4 -0
  26. django_cfg/apps/tasks/models/task_log.py +246 -0
  27. django_cfg/apps/tasks/serializers/__init__.py +28 -0
  28. django_cfg/apps/tasks/serializers/task_log.py +249 -0
  29. django_cfg/apps/tasks/services/__init__.py +10 -0
  30. django_cfg/apps/tasks/services/client/__init__.py +7 -0
  31. django_cfg/apps/tasks/services/client/client.py +234 -0
  32. django_cfg/apps/tasks/services/config_helper.py +63 -0
  33. django_cfg/apps/tasks/services/sync.py +204 -0
  34. django_cfg/apps/tasks/urls.py +7 -13
  35. django_cfg/apps/tasks/views/__init__.py +4 -10
  36. django_cfg/apps/tasks/views/task_log.py +41 -0
  37. django_cfg/apps/tasks/views/task_log_base.py +41 -0
  38. django_cfg/apps/tasks/views/task_log_overview.py +100 -0
  39. django_cfg/apps/tasks/views/task_log_related.py +41 -0
  40. django_cfg/apps/tasks/views/task_log_stats.py +91 -0
  41. django_cfg/apps/tasks/views/task_log_timeline.py +81 -0
  42. django_cfg/apps/urls.py +0 -1
  43. django_cfg/cli/commands/info.py +1 -1
  44. django_cfg/cli/utils.py +1 -1
  45. django_cfg/core/base/config_model.py +1 -1
  46. django_cfg/core/builders/apps_builder.py +1 -1
  47. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  48. django_cfg/core/generation/integration_generators/tasks.py +14 -18
  49. django_cfg/core/generation/security_generators/crypto_fields.py +2 -1
  50. django_cfg/core/integration/display/startup.py +1 -1
  51. django_cfg/mixins/__init__.py +12 -0
  52. django_cfg/mixins/admin_api.py +37 -0
  53. django_cfg/mixins/client_api.py +39 -0
  54. django_cfg/models/django/constance.py +2 -8
  55. django_cfg/models/django/crypto_fields.py +13 -48
  56. django_cfg/models/tasks/__init__.py +8 -10
  57. django_cfg/models/tasks/backends.py +76 -207
  58. django_cfg/models/tasks/config.py +20 -127
  59. django_cfg/models/tasks/utils.py +17 -29
  60. django_cfg/modules/django_client/management/commands/generate_client.py +13 -1
  61. django_cfg/modules/django_unfold/navigation.py +121 -22
  62. django_cfg/pyproject.toml +2 -2
  63. django_cfg/registry/core.py +1 -1
  64. django_cfg/static/frontend/admin.zip +0 -0
  65. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/METADATA +3 -3
  66. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/RECORD +70 -117
  67. django_cfg/apps/tasks/admin/actions.py +0 -29
  68. django_cfg/apps/tasks/admin/tasks_admin.py +0 -154
  69. django_cfg/apps/tasks/api/serializers.py +0 -82
  70. django_cfg/apps/tasks/api/views.py +0 -571
  71. django_cfg/apps/tasks/serializers.py +0 -82
  72. django_cfg/apps/tasks/static/tasks/css/dashboard-alpine.css +0 -299
  73. django_cfg/apps/tasks/static/tasks/css/dashboard.css +0 -120
  74. django_cfg/apps/tasks/static/tasks/js/alpine/README.md +0 -47
  75. django_cfg/apps/tasks/static/tasks/js/alpine/actions/index.js +0 -8
  76. django_cfg/apps/tasks/static/tasks/js/alpine/actions/management.js +0 -123
  77. django_cfg/apps/tasks/static/tasks/js/alpine/actions/pagination.js +0 -21
  78. django_cfg/apps/tasks/static/tasks/js/alpine/actions/tasks.js +0 -101
  79. django_cfg/apps/tasks/static/tasks/js/alpine/actions/workers.js +0 -59
  80. django_cfg/apps/tasks/static/tasks/js/alpine/computed.js +0 -35
  81. django_cfg/apps/tasks/static/tasks/js/alpine/index.js +0 -148
  82. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/index.js +0 -36
  83. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/overview.js +0 -37
  84. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/queues.js +0 -27
  85. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/tasks.js +0 -32
  86. django_cfg/apps/tasks/static/tasks/js/alpine/loaders/workers.js +0 -21
  87. django_cfg/apps/tasks/static/tasks/js/alpine/state.js +0 -36
  88. django_cfg/apps/tasks/static/tasks/js/alpine/utils/formatters.js +0 -42
  89. django_cfg/apps/tasks/static/tasks/js/alpine/utils/helpers.js +0 -68
  90. django_cfg/apps/tasks/static/tasks/js/dashboard-alpine.js +0 -725
  91. django_cfg/apps/tasks/tasks/__init__.py +0 -10
  92. django_cfg/apps/tasks/tasks/demo_tasks.py +0 -127
  93. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +0 -71
  94. django_cfg/apps/tasks/templates/tasks/components/overview_content.html +0 -94
  95. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +0 -44
  96. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +0 -45
  97. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -151
  98. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +0 -61
  99. django_cfg/apps/tasks/templates/tasks/components/tasks_mjs_integration.html +0 -269
  100. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +0 -60
  101. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -20
  102. django_cfg/apps/tasks/templates/tasks/pages/dashboard-improved.html +0 -168
  103. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +0 -77
  104. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +0 -40
  105. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +0 -40
  106. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +0 -86
  107. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +0 -90
  108. django_cfg/apps/tasks/urls_admin.py +0 -15
  109. django_cfg/apps/tasks/utils/__init__.py +0 -1
  110. django_cfg/apps/tasks/utils/simulator.py +0 -353
  111. django_cfg/apps/tasks/views/api.py +0 -571
  112. django_cfg/apps/tasks/views/dashboard.py +0 -89
  113. django_cfg/management/commands/rundramatiq.py +0 -24
  114. django_cfg/management/commands/rundramatiq_simulator.py +0 -22
  115. django_cfg/management/commands/task_clear.py +0 -25
  116. django_cfg/management/commands/task_status.py +0 -24
  117. django_cfg/modules/django_client/system/__init__.py +0 -24
  118. django_cfg/modules/django_client/system/base_generator.py +0 -123
  119. django_cfg/modules/django_client/system/generate_mjs_clients.py +0 -176
  120. django_cfg/modules/django_client/system/mjs_generator.py +0 -219
  121. django_cfg/modules/django_client/system/schema_parser.py +0 -199
  122. django_cfg/modules/django_client/system/templates/api_client.js.j2 +0 -87
  123. django_cfg/modules/django_client/system/templates/app_index.js.j2 +0 -13
  124. django_cfg/modules/django_client/system/templates/base_client.js.j2 +0 -166
  125. django_cfg/modules/django_client/system/templates/main_index.js.j2 +0 -80
  126. django_cfg/modules/django_client/system/templates/types.js.j2 +0 -24
  127. django_cfg/modules/django_tasks/__init__.py +0 -29
  128. django_cfg/modules/django_tasks/dramatiq_setup.py +0 -20
  129. django_cfg/modules/django_tasks/factory.py +0 -127
  130. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  131. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +0 -253
  132. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +0 -436
  133. django_cfg/modules/django_tasks/management/commands/task_clear.py +0 -226
  134. django_cfg/modules/django_tasks/management/commands/task_status.py +0 -257
  135. django_cfg/modules/django_tasks/service.py +0 -281
  136. django_cfg/modules/django_tasks/settings.py +0 -107
  137. /django_cfg/{modules/django_tasks/management → apps/tasks/migrations}/__init__.py +0 -0
  138. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/WHEEL +0 -0
  139. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/entry_points.txt +0 -0
  140. {django_cfg-1.4.107.dist-info → django_cfg-1.4.109.dist-info}/licenses/LICENSE +0 -0
@@ -1,86 +0,0 @@
1
- <!-- Task Footer Widget -->
2
- <div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 rounded-b-xl">
3
- <!-- Pagination (top section) -->
4
- <div class="flex items-center justify-between mb-4 pb-4 border-b border-gray-200 dark:border-gray-700" x-show="pagination.total_pages > 1">
5
- <div class="text-sm text-gray-600 dark:text-gray-400">
6
- Showing <span class="font-semibold" x-text="((pagination.page - 1) * pagination.page_size) + 1"></span>
7
- to <span class="font-semibold" x-text="Math.min(pagination.page * pagination.page_size, pagination.total_count)"></span>
8
- of <span class="font-semibold" x-text="pagination.total_count"></span> tasks
9
- </div>
10
-
11
- <div class="flex items-center space-x-2">
12
- <!-- Previous Button -->
13
- <button @click="previousPage()"
14
- :disabled="!pagination.has_previous"
15
- class="inline-flex items-center px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
16
- <span class="material-icons text-sm">chevron_left</span>
17
- Previous
18
- </button>
19
-
20
- <!-- Page Numbers -->
21
- <div class="flex items-center space-x-1">
22
- <template x-for="pageNum in Array.from({length: pagination.total_pages}, (_, i) => i + 1)" :key="pageNum">
23
- <button @click="goToPage(pageNum)"
24
- x-show="pageNum === 1 || pageNum === pagination.total_pages || Math.abs(pageNum - pagination.page) <= 2"
25
- :class="pageNum === pagination.page ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
26
- class="px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg transition-colors">
27
- <span x-text="pageNum"></span>
28
- </button>
29
- </template>
30
- </div>
31
-
32
- <!-- Next Button -->
33
- <button @click="nextPage()"
34
- :disabled="!pagination.has_next"
35
- class="inline-flex items-center px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
36
- Next
37
- <span class="material-icons text-sm">chevron_right</span>
38
- </button>
39
- </div>
40
- </div>
41
-
42
- <div class="flex items-center justify-between flex-wrap gap-4">
43
- <!-- Status Summary -->
44
- <div class="flex items-center space-x-6 text-sm">
45
- <div class="flex items-center">
46
- <div class="w-3 h-3 bg-green-500 rounded-full mr-2"></div>
47
- <span class="text-gray-600 dark:text-gray-400">Completed</span>
48
- <span class="ml-1 font-semibold text-gray-900 dark:text-white"
49
- x-text="tasks.filter(t => t.status === 'completed').length"></span>
50
- </div>
51
- <div class="flex items-center">
52
- <div class="w-3 h-3 bg-blue-500 rounded-full mr-2"></div>
53
- <span class="text-gray-600 dark:text-gray-400">Running</span>
54
- <span class="ml-1 font-semibold text-gray-900 dark:text-white"
55
- x-text="tasks.filter(t => t.status === 'running').length"></span>
56
- </div>
57
- <div class="flex items-center">
58
- <div class="w-3 h-3 bg-yellow-500 rounded-full mr-2"></div>
59
- <span class="text-gray-600 dark:text-gray-400">Pending</span>
60
- <span class="ml-1 font-semibold text-gray-900 dark:text-white"
61
- x-text="tasks.filter(t => t.status === 'pending').length"></span>
62
- </div>
63
- <div class="flex items-center">
64
- <div class="w-3 h-3 bg-red-500 rounded-full mr-2"></div>
65
- <span class="text-gray-600 dark:text-gray-400">Failed</span>
66
- <span class="ml-1 font-semibold text-gray-900 dark:text-white"
67
- x-text="tasks.filter(t => t.status === 'failed').length"></span>
68
- </div>
69
- </div>
70
-
71
- <!-- Actions -->
72
- <div class="flex items-center space-x-2">
73
- <button @click="clearCompletedTasks()"
74
- :disabled="loading || tasks.filter(t => t.status === 'completed').length === 0"
75
- class="px-3 py-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
76
- Clear Completed
77
- </button>
78
- <button @click="exportTasksCSV()"
79
- :disabled="loading || tasks.length === 0"
80
- class="inline-flex items-center px-3 py-2 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
81
- <span class="material-icons text-sm mr-1">download</span>
82
- Export CSV
83
- </button>
84
- </div>
85
- </div>
86
- </div>
@@ -1,90 +0,0 @@
1
- <!-- Task Table Widget -->
2
- <div class="task-table-widget overflow-x-auto custom-scrollbar">
3
- <table class="min-w-full bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
4
- <thead class="bg-gray-50 dark:bg-gray-700">
5
- <tr>
6
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-32">
7
- <div class="flex items-center space-x-1">
8
- <span>Status</span>
9
- </div>
10
- </th>
11
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
12
- <div class="flex items-center space-x-1">
13
- <span>Task</span>
14
- </div>
15
- </th>
16
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24">
17
- <div class="flex items-center space-x-1">
18
- <span>Queue</span>
19
- </div>
20
- </th>
21
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-20">
22
- Duration
23
- </th>
24
- <th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24">
25
- <div class="flex items-center space-x-1">
26
- <span>Updated</span>
27
- </div>
28
- </th>
29
- <th class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-32">
30
- Actions
31
- </th>
32
- </tr>
33
- </thead>
34
- <tbody class="divide-y divide-gray-200 dark:divide-gray-600">
35
- <template x-for="task in filteredTasks" :key="task.id || task.task_id || task.message_id">
36
- <tr class="task-row hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
37
- <!-- Status -->
38
- <td class="px-4 py-3">
39
- <span class="status-badge"
40
- :class="'status-badge ' + (task.status || 'pending')"
41
- x-text="task.status || 'pending'"></span>
42
- </td>
43
-
44
- <!-- Task Name -->
45
- <td class="px-4 py-3">
46
- <div class="text-sm font-medium text-gray-900 dark:text-white" x-text="task.name || task.actor_name || task.task_id"></div>
47
- <div class="text-xs text-gray-500 dark:text-gray-400" x-text="task.task_id || task.message_id"></div>
48
- </td>
49
-
50
- <!-- Queue -->
51
- <td class="px-4 py-3">
52
- <span class="text-sm text-gray-700 dark:text-gray-300" x-text="task.queue || task.queue_name || 'default'"></span>
53
- </td>
54
-
55
- <!-- Duration -->
56
- <td class="px-4 py-3">
57
- <span class="text-sm text-gray-700 dark:text-gray-300" x-text="formatDuration(task.duration)"></span>
58
- </td>
59
-
60
- <!-- Updated -->
61
- <td class="px-4 py-3">
62
- <span class="text-xs text-gray-500 dark:text-gray-400" x-text="formatDate(task.updated_at || task.created_at)"></span>
63
- </td>
64
-
65
- <!-- Actions -->
66
- <td class="px-4 py-3 text-right">
67
- <div class="flex items-center justify-end space-x-2">
68
- <button @click="viewTaskDetails(task)"
69
- class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
70
- title="View details">
71
- <span class="material-icons text-sm">visibility</span>
72
- </button>
73
- <button @click="retryTask(task)"
74
- x-show="task.status === 'failed'"
75
- class="text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-300"
76
- title="Retry task">
77
- <span class="material-icons text-sm">refresh</span>
78
- </button>
79
- <button @click="deleteTask(task)"
80
- class="text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
81
- title="Delete task">
82
- <span class="material-icons text-sm">delete</span>
83
- </button>
84
- </div>
85
- </td>
86
- </tr>
87
- </template>
88
- </tbody>
89
- </table>
90
- </div>
@@ -1,15 +0,0 @@
1
- """
2
- URLs for Django CFG Tasks app.
3
-
4
- Provides RESTful endpoints for task queue management and monitoring using ViewSets and routers.
5
- """
6
-
7
- from django.urls import path
8
-
9
- from .views import dashboard_view
10
-
11
- urlpatterns = [
12
-
13
- # Dashboard view
14
- path('dashboard/', dashboard_view, name='dashboard'),
15
- ]
@@ -1 +0,0 @@
1
- # Tasks utilities
@@ -1,353 +0,0 @@
1
- """
2
- Real Dramatiq data provider for Tasks Dashboard.
3
-
4
- This module provides real-time access to Dramatiq queue data from Redis,
5
- with optional test task generation for demonstration purposes.
6
- """
7
-
8
- import logging
9
- import random
10
- from datetime import datetime
11
- from typing import Any, Dict, Optional
12
- from urllib.parse import urlparse
13
-
14
- import redis
15
- from django.conf import settings
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class TaskSimulator:
21
- """Real Dramatiq data provider with test task generation capabilities."""
22
-
23
- def __init__(self):
24
- """Initialize with Redis connection for real Dramatiq data."""
25
- self.queues = ['critical', 'high', 'default', 'low', 'background', 'payments', 'agents', 'knowbase']
26
- self._redis_client = None
27
- self._setup_redis_connection()
28
-
29
- def _setup_redis_connection(self):
30
- """Setup Redis connection for real Dramatiq data."""
31
- try:
32
- # Get Redis URL from Django settings (DRAMATIQ_BROKER)
33
- dramatiq_config = getattr(settings, 'DRAMATIQ_BROKER', {})
34
- redis_url = dramatiq_config.get('OPTIONS', {}).get('url')
35
-
36
- if redis_url:
37
- parsed = urlparse(redis_url)
38
- self._redis_client = redis.Redis(
39
- host=parsed.hostname or 'localhost',
40
- port=parsed.port or 6379,
41
- db=int(parsed.path.lstrip('/')) if parsed.path else 1,
42
- decode_responses=True
43
- )
44
- # Test connection
45
- self._redis_client.ping()
46
- logger.info(f"Connected to Redis: {redis_url}")
47
- else:
48
- logger.warning("No Redis URL found in DRAMATIQ_BROKER settings")
49
-
50
- except Exception as e:
51
- logger.error(f"Failed to connect to Redis: {e}")
52
- self._redis_client = None
53
-
54
- def get_current_queue_status(self) -> Dict[str, Any]:
55
- """
56
- Get current queue status from real Dramatiq Redis data.
57
-
58
- Returns:
59
- Dict with queue status data
60
- """
61
- if not self._redis_client:
62
- return self._get_no_connection_response()
63
-
64
- try:
65
- queues_data = {}
66
- total_pending = 0
67
- total_failed = 0
68
- active_queues = 0
69
-
70
- # Get real queue data from Redis
71
- for queue_name in self.queues:
72
- # Get queue length (pending tasks) - Dramatiq uses dramatiq:{queue_name}.msgs format
73
- try:
74
- pending = self._redis_client.hlen(f'dramatiq:{queue_name}.msgs')
75
- except:
76
- pending = 0
77
-
78
- # Get failed queue length - Dramatiq uses dramatiq:{queue_name}.DQ format
79
- try:
80
- failed = self._redis_client.llen(f'dramatiq:{queue_name}.DQ')
81
- except:
82
- failed = 0
83
-
84
- if pending > 0 or failed > 0:
85
- active_queues += 1
86
- queues_data[queue_name] = {
87
- 'pending': pending,
88
- 'failed': failed,
89
- 'processed': 0, # Can't easily get this from Redis
90
- 'last_activity': datetime.now().isoformat()
91
- }
92
-
93
- total_pending += pending
94
- total_failed += failed
95
-
96
- # Get worker count from heartbeats - Dramatiq uses dramatiq:__heartbeats__ sorted set
97
- try:
98
- active_workers = self._redis_client.zcard('dramatiq:__heartbeats__')
99
- except:
100
- active_workers = 0
101
-
102
- return {
103
- 'queues': queues_data,
104
- 'workers': active_workers,
105
- 'redis_connected': True,
106
- 'timestamp': datetime.now().isoformat(),
107
- 'simulated': False,
108
- 'total_pending': total_pending,
109
- 'total_failed': total_failed,
110
- 'active_queues': active_queues
111
- }
112
-
113
- except Exception as e:
114
- logger.error(f"Error getting queue status: {e}")
115
- return self._get_error_response(str(e))
116
-
117
- def get_current_workers_list(self) -> Dict[str, Any]:
118
- """
119
- Get current workers list from real Dramatiq Redis data.
120
-
121
- Returns:
122
- Dict with workers data
123
- """
124
- if not self._redis_client:
125
- return {
126
- 'error': 'Redis connection not available',
127
- 'workers': [],
128
- 'active_count': 0,
129
- 'total_processed': 0,
130
- 'timestamp': datetime.now().isoformat(),
131
- 'simulated': False
132
- }
133
-
134
- try:
135
- workers = []
136
- # Get worker IDs from heartbeats - Dramatiq uses dramatiq:__heartbeats__ sorted set
137
- try:
138
- worker_ids = self._redis_client.zrange('dramatiq:__heartbeats__', 0, -1)
139
- except:
140
- worker_ids = []
141
-
142
- for worker_id in worker_ids:
143
- # Create worker info from heartbeat data
144
- workers.append({
145
- 'id': worker_id,
146
- 'name': f'worker-{worker_id[:8]}', # Short name
147
- 'pid': 'unknown', # Dramatiq doesn't store PID in heartbeats
148
- 'threads': 2, # Default threads
149
- 'tasks_processed': 0, # Can't get this from heartbeats
150
- 'started_at': datetime.now().isoformat(), # Approximate
151
- 'last_heartbeat': datetime.now().isoformat(),
152
- 'uptime': 'Active' # Since it's in heartbeats, it's active
153
- })
154
-
155
- return {
156
- 'workers': workers,
157
- 'active_count': len(workers),
158
- 'total_processed': sum(w['tasks_processed'] for w in workers),
159
- 'timestamp': datetime.now().isoformat(),
160
- 'simulated': False
161
- }
162
-
163
- except Exception as e:
164
- logger.error(f"Error getting workers list: {e}")
165
- return {
166
- 'error': f'Error getting workers list: {str(e)}',
167
- 'workers': [],
168
- 'active_count': 0,
169
- 'total_processed': 0,
170
- 'timestamp': datetime.now().isoformat(),
171
- 'simulated': False
172
- }
173
-
174
- def get_current_task_statistics(self) -> Dict[str, Any]:
175
- """
176
- Get current task statistics from real Dramatiq Redis data.
177
-
178
- Returns:
179
- Dict with task statistics data
180
- """
181
- if not self._redis_client:
182
- return {
183
- 'error': 'Redis connection not available',
184
- 'statistics': {'total': 0},
185
- 'recent_tasks': [],
186
- 'timestamp': datetime.now().isoformat(),
187
- 'simulated': False
188
- }
189
-
190
- try:
191
- # Calculate statistics from queue data
192
- queue_status = self.get_current_queue_status()
193
-
194
- total_pending = queue_status.get('total_pending', 0)
195
- total_failed = queue_status.get('total_failed', 0)
196
-
197
- # Estimate completed tasks (this is approximate)
198
- estimated_completed = random.randint(100, 1000) # Would need persistent storage for real data
199
-
200
- total_tasks = total_pending + total_failed + estimated_completed
201
-
202
- statistics = {
203
- 'total': total_tasks,
204
- 'completed': estimated_completed,
205
- 'failed': total_failed,
206
- 'pending': total_pending,
207
- 'average_duration': round(random.uniform(1.0, 5.0), 2), # Would need real tracking
208
- 'tasks_per_minute': round(random.uniform(5, 50), 1), # Would need real tracking
209
- 'success_rate': round((estimated_completed / max(total_tasks, 1)) * 100, 1) if total_tasks > 0 else 100
210
- }
211
-
212
- return {
213
- 'statistics': statistics,
214
- 'recent_tasks': [], # Would need task history storage for real data
215
- 'timestamp': datetime.now().isoformat(),
216
- 'simulated': False
217
- }
218
-
219
- except Exception as e:
220
- logger.error(f"Error getting task statistics: {e}")
221
- return {
222
- 'error': f'Error getting task statistics: {str(e)}',
223
- 'statistics': {'total': 0},
224
- 'recent_tasks': [],
225
- 'timestamp': datetime.now().isoformat(),
226
- 'simulated': False
227
- }
228
-
229
- def run_simulation(self, workers: int = 3, clear_first: bool = True) -> Dict[str, Any]:
230
- """
231
- Generate test tasks for demonstration purposes.
232
-
233
- Args:
234
- workers: Number of test tasks to generate (ignored, kept for compatibility)
235
- clear_first: Whether to clear existing tasks first (ignored)
236
-
237
- Returns:
238
- Dict with simulation results
239
- """
240
- try:
241
- # Generate realistic test tasks using Dramatiq
242
- tasks_created = self._generate_test_tasks()
243
-
244
- return {
245
- 'success': True,
246
- 'message': f'Generated {tasks_created} test tasks for demonstration',
247
- 'details': {
248
- 'tasks_created': tasks_created,
249
- 'queues_used': self.queues,
250
- 'timestamp': datetime.now().isoformat()
251
- }
252
- }
253
-
254
- except Exception as e:
255
- logger.error(f"Test task generation failed: {e}")
256
- return {
257
- 'success': False,
258
- 'error': f'Test task generation failed: {str(e)}'
259
- }
260
-
261
- def clear_all_data(self) -> Dict[str, Any]:
262
- """
263
- Clear all tasks from Dramatiq queues.
264
-
265
- Returns:
266
- Dict with clear operation results
267
- """
268
- if not self._redis_client:
269
- return {
270
- 'success': False,
271
- 'error': 'Redis connection not available'
272
- }
273
-
274
- try:
275
- cleared_count = 0
276
-
277
- # Clear all queue data
278
- for queue_name in self.queues:
279
- # Clear main queue - Dramatiq uses dramatiq:{queue_name} format
280
- main_queue_len = self._redis_client.llen(f'dramatiq:{queue_name}')
281
- if main_queue_len > 0:
282
- self._redis_client.delete(f'dramatiq:{queue_name}')
283
- cleared_count += main_queue_len
284
-
285
- # Clear failed queue - Dramatiq uses dramatiq:{queue_name}.DQ format
286
- failed_queue_len = self._redis_client.llen(f'dramatiq:{queue_name}.DQ')
287
- if failed_queue_len > 0:
288
- self._redis_client.delete(f'dramatiq:{queue_name}.DQ')
289
- cleared_count += failed_queue_len
290
-
291
- return {
292
- 'success': True,
293
- 'message': f'Cleared {cleared_count} tasks from all queues',
294
- 'cleared_tasks': cleared_count
295
- }
296
-
297
- except Exception as e:
298
- logger.error(f"Clear operation failed: {e}")
299
- return {
300
- 'success': False,
301
- 'error': f'Clear operation failed: {str(e)}'
302
- }
303
-
304
- def _generate_test_tasks(self) -> int:
305
- """Generate realistic test tasks for demonstration."""
306
- try:
307
- # Import and use the demo tasks module
308
- from ..tasks.demo_tasks import generate_demo_tasks
309
- return generate_demo_tasks()
310
- except ImportError as e:
311
- logger.error(f"Failed to import demo tasks: {e}")
312
- return 0
313
-
314
- def _calculate_uptime(self, started_at: Optional[str]) -> str:
315
- """Calculate uptime from start time."""
316
- if not started_at:
317
- return 'Unknown'
318
-
319
- try:
320
- start_time = datetime.fromisoformat(started_at.replace('Z', '+00:00'))
321
- uptime = datetime.now() - start_time.replace(tzinfo=None)
322
-
323
- hours = int(uptime.total_seconds() // 3600)
324
- minutes = int((uptime.total_seconds() % 3600) // 60)
325
-
326
- if hours > 0:
327
- return f'{hours}h {minutes}m'
328
- else:
329
- return f'{minutes}m'
330
- except:
331
- return 'Unknown'
332
-
333
- def _get_no_connection_response(self) -> Dict[str, Any]:
334
- """Return response when Redis connection is not available."""
335
- return {
336
- 'error': 'Redis connection not available - check DRAMATIQ_BROKER settings',
337
- 'queues': {},
338
- 'workers': 0,
339
- 'redis_connected': False,
340
- 'timestamp': datetime.now().isoformat(),
341
- 'simulated': False
342
- }
343
-
344
- def _get_error_response(self, error_message: str) -> Dict[str, Any]:
345
- """Return error response."""
346
- return {
347
- 'error': f'Error getting queue status: {error_message}',
348
- 'queues': {},
349
- 'workers': 0,
350
- 'redis_connected': False,
351
- 'timestamp': datetime.now().isoformat(),
352
- 'simulated': False
353
- }