django-cfg 1.3.7__py3-none-any.whl → 1.3.11__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -1,454 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Task Monitor Modal
|
3
|
-
* Interactive task monitoring with real-time updates
|
4
|
-
*/
|
5
|
-
class TaskMonitor {
|
6
|
-
constructor() {
|
7
|
-
this.modal = null;
|
8
|
-
this.autoRefresh = true;
|
9
|
-
this.refreshInterval = null;
|
10
|
-
this.refreshRate = 5000; // 5 seconds
|
11
|
-
this.currentFilters = {
|
12
|
-
status: '',
|
13
|
-
queue: '',
|
14
|
-
search: ''
|
15
|
-
};
|
16
|
-
this.tasks = [];
|
17
|
-
|
18
|
-
this.init();
|
19
|
-
}
|
20
|
-
|
21
|
-
init() {
|
22
|
-
this.modal = document.getElementById('task-details-modal');
|
23
|
-
if (!this.modal) return;
|
24
|
-
|
25
|
-
this.setupEventListeners();
|
26
|
-
this.startAutoRefresh();
|
27
|
-
}
|
28
|
-
|
29
|
-
setupEventListeners() {
|
30
|
-
// Modal controls
|
31
|
-
document.getElementById('close-task-modal')?.addEventListener('click', () => this.hide());
|
32
|
-
document.getElementById('refresh-tasks-modal')?.addEventListener('click', () => this.loadTasks());
|
33
|
-
document.getElementById('auto-refresh-toggle')?.addEventListener('click', () => this.toggleAutoRefresh());
|
34
|
-
|
35
|
-
// Filters
|
36
|
-
document.getElementById('modal-status-filter')?.addEventListener('change', (e) => {
|
37
|
-
this.currentFilters.status = e.target.value;
|
38
|
-
this.applyFilters();
|
39
|
-
});
|
40
|
-
|
41
|
-
document.getElementById('modal-queue-filter')?.addEventListener('change', (e) => {
|
42
|
-
this.currentFilters.queue = e.target.value;
|
43
|
-
this.applyFilters();
|
44
|
-
});
|
45
|
-
|
46
|
-
document.getElementById('modal-search-input')?.addEventListener('input', (e) => {
|
47
|
-
this.currentFilters.search = e.target.value.toLowerCase();
|
48
|
-
this.applyFilters();
|
49
|
-
});
|
50
|
-
|
51
|
-
// Actions
|
52
|
-
document.getElementById('clear-completed-tasks')?.addEventListener('click', () => this.clearCompletedTasks());
|
53
|
-
document.getElementById('export-tasks')?.addEventListener('click', () => this.exportTasks());
|
54
|
-
|
55
|
-
// Close modal on outside click
|
56
|
-
this.modal.addEventListener('click', (e) => {
|
57
|
-
if (e.target === this.modal) {
|
58
|
-
this.hide();
|
59
|
-
}
|
60
|
-
});
|
61
|
-
|
62
|
-
// Close modal on Escape key
|
63
|
-
document.addEventListener('keydown', (e) => {
|
64
|
-
if (e.key === 'Escape' && !this.modal.classList.contains('hidden')) {
|
65
|
-
this.hide();
|
66
|
-
}
|
67
|
-
});
|
68
|
-
}
|
69
|
-
|
70
|
-
show() {
|
71
|
-
if (!this.modal) return;
|
72
|
-
|
73
|
-
this.modal.classList.remove('hidden');
|
74
|
-
document.body.style.overflow = 'hidden';
|
75
|
-
this.loadTasks();
|
76
|
-
|
77
|
-
if (this.autoRefresh) {
|
78
|
-
this.startAutoRefresh();
|
79
|
-
}
|
80
|
-
}
|
81
|
-
|
82
|
-
hide() {
|
83
|
-
if (!this.modal) return;
|
84
|
-
|
85
|
-
this.modal.classList.add('hidden');
|
86
|
-
document.body.style.overflow = '';
|
87
|
-
this.stopAutoRefresh();
|
88
|
-
}
|
89
|
-
|
90
|
-
toggleAutoRefresh() {
|
91
|
-
this.autoRefresh = !this.autoRefresh;
|
92
|
-
const button = document.getElementById('auto-refresh-toggle');
|
93
|
-
|
94
|
-
if (this.autoRefresh) {
|
95
|
-
button.innerHTML = '<span class="material-icons text-sm mr-1">refresh</span>Auto: ON';
|
96
|
-
button.className = 'px-3 py-1 text-sm bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-lg hover:bg-green-200 dark:hover:bg-green-800 transition-colors';
|
97
|
-
this.startAutoRefresh();
|
98
|
-
} else {
|
99
|
-
button.innerHTML = '<span class="material-icons text-sm mr-1">pause</span>Auto: OFF';
|
100
|
-
button.className = 'px-3 py-1 text-sm bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors';
|
101
|
-
this.stopAutoRefresh();
|
102
|
-
}
|
103
|
-
}
|
104
|
-
|
105
|
-
startAutoRefresh() {
|
106
|
-
if (this.refreshInterval) {
|
107
|
-
clearInterval(this.refreshInterval);
|
108
|
-
}
|
109
|
-
|
110
|
-
if (this.autoRefresh) {
|
111
|
-
this.refreshInterval = setInterval(() => {
|
112
|
-
if (!this.modal.classList.contains('hidden')) {
|
113
|
-
this.loadTasks();
|
114
|
-
}
|
115
|
-
}, this.refreshRate);
|
116
|
-
}
|
117
|
-
}
|
118
|
-
|
119
|
-
stopAutoRefresh() {
|
120
|
-
if (this.refreshInterval) {
|
121
|
-
clearInterval(this.refreshInterval);
|
122
|
-
this.refreshInterval = null;
|
123
|
-
}
|
124
|
-
}
|
125
|
-
|
126
|
-
async loadTasks() {
|
127
|
-
try {
|
128
|
-
this.showLoading();
|
129
|
-
|
130
|
-
// Get real task data from API
|
131
|
-
const params = {
|
132
|
-
limit: 100,
|
133
|
-
offset: 0,
|
134
|
-
...this.currentFilters
|
135
|
-
};
|
136
|
-
|
137
|
-
const response = await window.tasksAPI.getTaskList(params);
|
138
|
-
const data = response.data || response;
|
139
|
-
|
140
|
-
if (data.error) {
|
141
|
-
// Fallback to mock data if API fails
|
142
|
-
console.warn('API error, using mock data:', data.error);
|
143
|
-
this.tasks = this.generateMockTasks();
|
144
|
-
} else {
|
145
|
-
this.tasks = data.tasks || [];
|
146
|
-
}
|
147
|
-
|
148
|
-
this.updateTaskCount();
|
149
|
-
this.updateLastUpdateTime();
|
150
|
-
this.renderTasks();
|
151
|
-
|
152
|
-
} catch (error) {
|
153
|
-
console.error('Failed to load tasks:', error);
|
154
|
-
// Fallback to mock data on error
|
155
|
-
this.tasks = this.generateMockTasks();
|
156
|
-
this.updateTaskCount();
|
157
|
-
this.updateLastUpdateTime();
|
158
|
-
this.renderTasks();
|
159
|
-
}
|
160
|
-
}
|
161
|
-
|
162
|
-
generateMockTasks() {
|
163
|
-
// Generate mock task data - replace with real API data
|
164
|
-
const statuses = ['pending', 'running', 'done', 'failed'];
|
165
|
-
const queues = ['default', 'high', 'low', 'vehicles'];
|
166
|
-
const actors = ['process_document_async', 'send_notification', 'cleanup_old_files', 'generate_report'];
|
167
|
-
|
168
|
-
return Array.from({ length: 20 }, (_, i) => ({
|
169
|
-
id: `task_${i + 1}`,
|
170
|
-
actor_name: actors[Math.floor(Math.random() * actors.length)],
|
171
|
-
status: statuses[Math.floor(Math.random() * statuses.length)],
|
172
|
-
queue: queues[Math.floor(Math.random() * queues.length)],
|
173
|
-
created_at: new Date(Date.now() - Math.random() * 86400000).toISOString(),
|
174
|
-
updated_at: new Date(Date.now() - Math.random() * 3600000).toISOString(),
|
175
|
-
args: JSON.stringify([Math.floor(Math.random() * 1000)]),
|
176
|
-
kwargs: JSON.stringify({ user_id: Math.floor(Math.random() * 100) }),
|
177
|
-
progress: Math.floor(Math.random() * 100),
|
178
|
-
result: Math.random() > 0.7 ? JSON.stringify({ success: true, processed: Math.floor(Math.random() * 100) }) : null,
|
179
|
-
traceback: Math.random() > 0.9 ? 'Error: Something went wrong...' : null
|
180
|
-
}));
|
181
|
-
}
|
182
|
-
|
183
|
-
applyFilters() {
|
184
|
-
this.renderTasks();
|
185
|
-
}
|
186
|
-
|
187
|
-
getFilteredTasks() {
|
188
|
-
return this.tasks.filter(task => {
|
189
|
-
const matchesStatus = !this.currentFilters.status || task.status === this.currentFilters.status;
|
190
|
-
const matchesQueue = !this.currentFilters.queue || task.queue === this.currentFilters.queue;
|
191
|
-
const matchesSearch = !this.currentFilters.search ||
|
192
|
-
task.actor_name.toLowerCase().includes(this.currentFilters.search) ||
|
193
|
-
task.id.toLowerCase().includes(this.currentFilters.search);
|
194
|
-
|
195
|
-
return matchesStatus && matchesQueue && matchesSearch;
|
196
|
-
});
|
197
|
-
}
|
198
|
-
|
199
|
-
renderTasks() {
|
200
|
-
const container = document.getElementById('tasks-list');
|
201
|
-
const loading = document.getElementById('tasks-loading');
|
202
|
-
const empty = document.getElementById('tasks-empty');
|
203
|
-
|
204
|
-
if (!container) return;
|
205
|
-
|
206
|
-
loading.classList.add('hidden');
|
207
|
-
|
208
|
-
const filteredTasks = this.getFilteredTasks();
|
209
|
-
|
210
|
-
if (filteredTasks.length === 0) {
|
211
|
-
container.innerHTML = '';
|
212
|
-
empty.classList.remove('hidden');
|
213
|
-
return;
|
214
|
-
}
|
215
|
-
|
216
|
-
empty.classList.add('hidden');
|
217
|
-
|
218
|
-
// Create table layout
|
219
|
-
container.innerHTML = `
|
220
|
-
<div class="overflow-x-auto">
|
221
|
-
<table class="min-w-full bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
222
|
-
<thead class="bg-gray-50 dark:bg-gray-700">
|
223
|
-
<tr>
|
224
|
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-32">Status</th>
|
225
|
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Task</th>
|
226
|
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24">Queue</th>
|
227
|
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-20">Duration</th>
|
228
|
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24">Updated</th>
|
229
|
-
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-32">Actions</th>
|
230
|
-
</tr>
|
231
|
-
</thead>
|
232
|
-
<tbody class="divide-y divide-gray-200 dark:divide-gray-600">
|
233
|
-
${filteredTasks.map(task => this.renderTaskRow(task)).join('')}
|
234
|
-
</tbody>
|
235
|
-
</table>
|
236
|
-
</div>
|
237
|
-
`;
|
238
|
-
}
|
239
|
-
|
240
|
-
renderTaskRow(task) {
|
241
|
-
// Map Dramatiq statuses to our display statuses
|
242
|
-
const statusMap = {
|
243
|
-
'enqueued': 'pending',
|
244
|
-
'delayed': 'pending',
|
245
|
-
'running': 'running',
|
246
|
-
'done': 'done',
|
247
|
-
'failed': 'failed',
|
248
|
-
'skipped': 'done'
|
249
|
-
};
|
250
|
-
|
251
|
-
const displayStatus = statusMap[task.status.toLowerCase()] || task.status.toLowerCase();
|
252
|
-
|
253
|
-
const statusColors = {
|
254
|
-
pending: 'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200',
|
255
|
-
running: 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200',
|
256
|
-
done: 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200',
|
257
|
-
failed: 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200'
|
258
|
-
};
|
259
|
-
|
260
|
-
const statusIcons = {
|
261
|
-
pending: 'schedule',
|
262
|
-
running: 'play_circle',
|
263
|
-
done: 'check_circle',
|
264
|
-
failed: 'error'
|
265
|
-
};
|
266
|
-
|
267
|
-
const statusLabels = {
|
268
|
-
pending: task.status.toLowerCase() === 'delayed' ? 'DELAYED' : 'PENDING',
|
269
|
-
running: 'RUNNING',
|
270
|
-
done: 'DONE',
|
271
|
-
failed: 'FAILED'
|
272
|
-
};
|
273
|
-
|
274
|
-
const duration = this.calculateDuration(task.created_at, task.updated_at);
|
275
|
-
const updatedTime = new Date(task.updated_at).toLocaleTimeString();
|
276
|
-
|
277
|
-
return `
|
278
|
-
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
279
|
-
<!-- Status -->
|
280
|
-
<td class="px-4 py-3 whitespace-nowrap">
|
281
|
-
<div class="flex items-center space-x-2">
|
282
|
-
<span class="material-icons text-sm ${displayStatus === 'failed' ? 'text-red-500' : displayStatus === 'done' ? 'text-green-500' : displayStatus === 'running' ? 'text-blue-500' : 'text-yellow-500'}">${statusIcons[displayStatus]}</span>
|
283
|
-
<span class="px-2 py-1 text-xs font-medium rounded-full ${statusColors[displayStatus]}">${statusLabels[displayStatus]}</span>
|
284
|
-
</div>
|
285
|
-
${displayStatus === 'running' && task.progress ? `
|
286
|
-
<div class="mt-1 w-16 bg-gray-200 dark:bg-gray-600 rounded-full h-1">
|
287
|
-
<div class="bg-blue-600 h-1 rounded-full transition-all duration-300" style="width: ${task.progress}%"></div>
|
288
|
-
</div>
|
289
|
-
` : ''}
|
290
|
-
</td>
|
291
|
-
|
292
|
-
<!-- Task -->
|
293
|
-
<td class="px-4 py-3">
|
294
|
-
<div class="text-sm font-medium text-gray-900 dark:text-white">${task.actor_name}</div>
|
295
|
-
<div class="text-xs text-gray-500 dark:text-gray-400 font-mono">${task.id.substring(0, 8)}...</div>
|
296
|
-
</td>
|
297
|
-
|
298
|
-
<!-- Queue -->
|
299
|
-
<td class="px-4 py-3 whitespace-nowrap">
|
300
|
-
<span class="px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-full">${task.queue}</span>
|
301
|
-
</td>
|
302
|
-
|
303
|
-
<!-- Duration -->
|
304
|
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">${duration}</td>
|
305
|
-
|
306
|
-
<!-- Updated -->
|
307
|
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">${updatedTime}</td>
|
308
|
-
|
309
|
-
<!-- Actions -->
|
310
|
-
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
|
311
|
-
<div class="flex items-center justify-end space-x-1">
|
312
|
-
${task.args || task.kwargs ? `
|
313
|
-
<button class="p-1 text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded transition-colors"
|
314
|
-
onclick="this.classList.toggle('active'); const row = this.closest('tr'); const nextRow = row.nextElementSibling; if (nextRow && nextRow.classList.contains('details-row')) { nextRow.remove(); } else { row.insertAdjacentHTML('afterend', \`<tr class='details-row'><td colspan='6' class='px-4 py-2 bg-gray-50 dark:bg-gray-900 text-xs border-t border-gray-200 dark:border-gray-600'><strong>Args:</strong> \${task.args || 'None'}<br><strong>Kwargs:</strong> \${task.kwargs || 'None'}</td></tr>\`); }"
|
315
|
-
title="Show arguments">
|
316
|
-
<span class="material-icons text-sm">code</span>
|
317
|
-
</button>
|
318
|
-
` : ''}
|
319
|
-
${task.result ? `
|
320
|
-
<button class="p-1 text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 hover:bg-green-50 dark:hover:bg-green-900/20 rounded transition-colors"
|
321
|
-
onclick="alert('Result: ' + \`${task.result}\`)"
|
322
|
-
title="Show result">
|
323
|
-
<span class="material-icons text-sm">check_circle</span>
|
324
|
-
</button>
|
325
|
-
` : ''}
|
326
|
-
${task.traceback ? `
|
327
|
-
<button class="p-1 text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors"
|
328
|
-
onclick="alert('Error: ' + \`${task.traceback}\`)"
|
329
|
-
title="Show error">
|
330
|
-
<span class="material-icons text-sm">error</span>
|
331
|
-
</button>
|
332
|
-
` : ''}
|
333
|
-
${displayStatus === 'failed' ? `
|
334
|
-
<button class="px-2 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors ml-1">
|
335
|
-
Retry
|
336
|
-
</button>
|
337
|
-
` : ''}
|
338
|
-
</div>
|
339
|
-
</td>
|
340
|
-
</tr>
|
341
|
-
`;
|
342
|
-
}
|
343
|
-
|
344
|
-
calculateDuration(startTime, endTime) {
|
345
|
-
const start = new Date(startTime);
|
346
|
-
const end = new Date(endTime);
|
347
|
-
const diff = end - start;
|
348
|
-
|
349
|
-
if (diff < 1000) return `${diff}ms`;
|
350
|
-
if (diff < 60000) return `${Math.floor(diff / 1000)}s`;
|
351
|
-
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ${Math.floor((diff % 60000) / 1000)}s`;
|
352
|
-
return `${Math.floor(diff / 3600000)}h ${Math.floor((diff % 3600000) / 60000)}m`;
|
353
|
-
}
|
354
|
-
|
355
|
-
updateTaskCount() {
|
356
|
-
const badge = document.getElementById('task-count-badge');
|
357
|
-
if (badge) {
|
358
|
-
const filteredCount = this.getFilteredTasks().length;
|
359
|
-
const totalCount = this.tasks.length;
|
360
|
-
badge.textContent = filteredCount === totalCount ?
|
361
|
-
`${totalCount} tasks` :
|
362
|
-
`${filteredCount} of ${totalCount} tasks`;
|
363
|
-
}
|
364
|
-
}
|
365
|
-
|
366
|
-
updateLastUpdateTime() {
|
367
|
-
const timeElement = document.getElementById('last-update-time');
|
368
|
-
if (timeElement) {
|
369
|
-
timeElement.textContent = new Date().toLocaleTimeString();
|
370
|
-
}
|
371
|
-
}
|
372
|
-
|
373
|
-
showLoading() {
|
374
|
-
const loading = document.getElementById('tasks-loading');
|
375
|
-
const empty = document.getElementById('tasks-empty');
|
376
|
-
const list = document.getElementById('tasks-list');
|
377
|
-
|
378
|
-
loading?.classList.remove('hidden');
|
379
|
-
empty?.classList.add('hidden');
|
380
|
-
if (list) list.innerHTML = '';
|
381
|
-
}
|
382
|
-
|
383
|
-
showError(message) {
|
384
|
-
const container = document.getElementById('tasks-list');
|
385
|
-
if (container) {
|
386
|
-
container.innerHTML = `
|
387
|
-
<div class="text-center py-12">
|
388
|
-
<span class="material-icons text-6xl text-red-400 dark:text-red-600 mb-4">error</span>
|
389
|
-
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Error Loading Tasks</h3>
|
390
|
-
<p class="text-gray-500 dark:text-gray-400">${message}</p>
|
391
|
-
</div>
|
392
|
-
`;
|
393
|
-
}
|
394
|
-
}
|
395
|
-
|
396
|
-
async clearCompletedTasks() {
|
397
|
-
if (!confirm('Are you sure you want to clear all completed tasks?')) {
|
398
|
-
return;
|
399
|
-
}
|
400
|
-
|
401
|
-
try {
|
402
|
-
// TODO: Implement API call to clear completed tasks
|
403
|
-
console.log('Clearing completed tasks...');
|
404
|
-
this.loadTasks();
|
405
|
-
} catch (error) {
|
406
|
-
console.error('Failed to clear completed tasks:', error);
|
407
|
-
}
|
408
|
-
}
|
409
|
-
|
410
|
-
exportTasks() {
|
411
|
-
const filteredTasks = this.getFilteredTasks();
|
412
|
-
const csv = this.tasksToCSV(filteredTasks);
|
413
|
-
this.downloadCSV(csv, 'tasks-export.csv');
|
414
|
-
}
|
415
|
-
|
416
|
-
tasksToCSV(tasks) {
|
417
|
-
const headers = ['ID', 'Actor', 'Status', 'Queue', 'Created', 'Updated', 'Duration'];
|
418
|
-
const rows = tasks.map(task => [
|
419
|
-
task.id,
|
420
|
-
task.actor_name,
|
421
|
-
task.status,
|
422
|
-
task.queue,
|
423
|
-
new Date(task.created_at).toLocaleString(),
|
424
|
-
new Date(task.updated_at).toLocaleString(),
|
425
|
-
this.calculateDuration(task.created_at, task.updated_at)
|
426
|
-
]);
|
427
|
-
|
428
|
-
return [headers, ...rows].map(row =>
|
429
|
-
row.map(field => `"${field}"`).join(',')
|
430
|
-
).join('\n');
|
431
|
-
}
|
432
|
-
|
433
|
-
downloadCSV(csv, filename) {
|
434
|
-
const blob = new Blob([csv], { type: 'text/csv' });
|
435
|
-
const url = window.URL.createObjectURL(blob);
|
436
|
-
const a = document.createElement('a');
|
437
|
-
a.href = url;
|
438
|
-
a.download = filename;
|
439
|
-
a.click();
|
440
|
-
window.URL.revokeObjectURL(url);
|
441
|
-
}
|
442
|
-
}
|
443
|
-
|
444
|
-
// Initialize task monitor when DOM is loaded
|
445
|
-
document.addEventListener('DOMContentLoaded', () => {
|
446
|
-
window.taskMonitor = new TaskMonitor();
|
447
|
-
});
|
448
|
-
|
449
|
-
// Global function to show task monitor
|
450
|
-
window.showTaskMonitor = () => {
|
451
|
-
if (window.taskMonitor) {
|
452
|
-
window.taskMonitor.show();
|
453
|
-
}
|
454
|
-
};
|
@@ -1,77 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Theme Toggle Functionality
|
3
|
-
* Handles dark/light mode switching
|
4
|
-
*/
|
5
|
-
|
6
|
-
class ThemeManager {
|
7
|
-
constructor() {
|
8
|
-
this.init();
|
9
|
-
}
|
10
|
-
|
11
|
-
init() {
|
12
|
-
// Get saved theme or default to light
|
13
|
-
this.currentTheme = localStorage.getItem('theme') || 'light';
|
14
|
-
|
15
|
-
// Apply theme on load
|
16
|
-
this.applyTheme(this.currentTheme);
|
17
|
-
|
18
|
-
// Setup toggle button
|
19
|
-
this.setupToggle();
|
20
|
-
}
|
21
|
-
|
22
|
-
applyTheme(theme) {
|
23
|
-
const html = document.documentElement;
|
24
|
-
const toggleButton = document.getElementById('theme-toggle');
|
25
|
-
const icon = toggleButton?.querySelector('.material-icons');
|
26
|
-
|
27
|
-
console.log('Applying theme:', theme);
|
28
|
-
console.log('Toggle button found:', !!toggleButton);
|
29
|
-
console.log('Icon found:', !!icon);
|
30
|
-
|
31
|
-
if (theme === 'dark') {
|
32
|
-
html.classList.add('dark');
|
33
|
-
if (icon) {
|
34
|
-
icon.textContent = 'dark_mode';
|
35
|
-
}
|
36
|
-
} else {
|
37
|
-
html.classList.remove('dark');
|
38
|
-
if (icon) {
|
39
|
-
icon.textContent = 'light_mode';
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
// Save to localStorage
|
44
|
-
localStorage.setItem('theme', theme);
|
45
|
-
this.currentTheme = theme;
|
46
|
-
|
47
|
-
console.log('Theme applied. Current classes:', html.classList.toString());
|
48
|
-
}
|
49
|
-
|
50
|
-
toggle() {
|
51
|
-
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
|
52
|
-
this.applyTheme(newTheme);
|
53
|
-
}
|
54
|
-
|
55
|
-
setupToggle() {
|
56
|
-
const toggleButton = document.getElementById('theme-toggle');
|
57
|
-
if (toggleButton) {
|
58
|
-
toggleButton.addEventListener('click', () => this.toggle());
|
59
|
-
console.log('Theme toggle button found and event listener added');
|
60
|
-
} else {
|
61
|
-
console.warn('Theme toggle button not found');
|
62
|
-
// Try to find it after a short delay
|
63
|
-
setTimeout(() => {
|
64
|
-
const delayedButton = document.getElementById('theme-toggle');
|
65
|
-
if (delayedButton) {
|
66
|
-
delayedButton.addEventListener('click', () => this.toggle());
|
67
|
-
console.log('Theme toggle button found after delay');
|
68
|
-
}
|
69
|
-
}, 100);
|
70
|
-
}
|
71
|
-
}
|
72
|
-
}
|
73
|
-
|
74
|
-
// Initialize theme manager when DOM is loaded
|
75
|
-
document.addEventListener('DOMContentLoaded', () => {
|
76
|
-
window.themeManager = new ThemeManager();
|
77
|
-
});
|
@@ -1,96 +0,0 @@
|
|
1
|
-
<!DOCTYPE html>
|
2
|
-
<html lang="en" class="h-full">
|
3
|
-
<head>
|
4
|
-
<meta charset="UTF-8">
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
-
<title>{% block title %}Dramatiq Tasks Dashboard{% endblock %}</title>
|
7
|
-
|
8
|
-
<!-- Tailwind CSS -->
|
9
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
10
|
-
<script>
|
11
|
-
tailwind.config = {
|
12
|
-
darkMode: 'class',
|
13
|
-
theme: {
|
14
|
-
extend: {
|
15
|
-
colors: {
|
16
|
-
primary: {
|
17
|
-
50: '#eff6ff',
|
18
|
-
100: '#dbeafe',
|
19
|
-
200: '#bfdbfe',
|
20
|
-
300: '#93c5fd',
|
21
|
-
400: '#60a5fa',
|
22
|
-
500: '#3b82f6',
|
23
|
-
600: '#2563eb',
|
24
|
-
700: '#1d4ed8',
|
25
|
-
800: '#1e40af',
|
26
|
-
900: '#1e3a8a',
|
27
|
-
}
|
28
|
-
}
|
29
|
-
}
|
30
|
-
}
|
31
|
-
}
|
32
|
-
</script>
|
33
|
-
|
34
|
-
<!-- Material Icons -->
|
35
|
-
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
36
|
-
|
37
|
-
<!-- Custom CSS -->
|
38
|
-
{% load static %}
|
39
|
-
<link rel="stylesheet" href="{% static 'tasks/css/dashboard.css' %}">
|
40
|
-
|
41
|
-
{% block extra_head %}{% endblock %}
|
42
|
-
</head>
|
43
|
-
<body class="h-full bg-gray-50 dark:bg-gray-900">
|
44
|
-
<div class="min-h-full">
|
45
|
-
<!-- Header -->
|
46
|
-
<header class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
|
47
|
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
48
|
-
<div class="flex items-center justify-between h-16">
|
49
|
-
<div class="flex items-center space-x-3">
|
50
|
-
<div class="flex-shrink-0">
|
51
|
-
<span class="material-icons text-primary-600 dark:text-primary-400 text-3xl">analytics</span>
|
52
|
-
</div>
|
53
|
-
<div>
|
54
|
-
<h1 class="text-xl font-bold text-gray-900 dark:text-white">
|
55
|
-
{% block header_title %}Dramatiq Tasks Dashboard{% endblock %}
|
56
|
-
</h1>
|
57
|
-
<p class="text-sm text-gray-500 dark:text-gray-400">
|
58
|
-
{% block header_subtitle %}Monitor and manage background task queues{% endblock %}
|
59
|
-
</p>
|
60
|
-
</div>
|
61
|
-
</div>
|
62
|
-
<div class="flex items-center space-x-3">
|
63
|
-
{% block header_actions %}
|
64
|
-
<a href="/admin/django_dramatiq/task/"
|
65
|
-
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors duration-200">
|
66
|
-
<span class="material-icons text-sm mr-2">history</span>
|
67
|
-
Task History
|
68
|
-
</a>
|
69
|
-
<button id="theme-toggle"
|
70
|
-
class="group inline-flex items-center justify-center px-3 py-2 rounded-md bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
71
|
-
<span class="material-icons text-gray-600 dark:text-yellow-400 group-hover:text-primary-600 dark:group-hover:text-yellow-300 transition-colors duration-200">light_mode</span>
|
72
|
-
</button>
|
73
|
-
{% endblock %}
|
74
|
-
</div>
|
75
|
-
</div>
|
76
|
-
</div>
|
77
|
-
</header>
|
78
|
-
|
79
|
-
<!-- Main content -->
|
80
|
-
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
81
|
-
{% block content %}{% endblock %}
|
82
|
-
</main>
|
83
|
-
</div>
|
84
|
-
|
85
|
-
<!-- Notification Container -->
|
86
|
-
<div id="notification-container" class="fixed top-4 left-1/2 transform -translate-x-1/2 z-50 space-y-2"></div>
|
87
|
-
|
88
|
-
<!-- Modal Container -->
|
89
|
-
<div id="modal-container"></div>
|
90
|
-
|
91
|
-
<!-- JavaScript -->
|
92
|
-
<script src="{% static 'tasks/js/theme.js' %}"></script>
|
93
|
-
<script src="{% static 'tasks/js/notifications.js' %}"></script>
|
94
|
-
{% block extra_js %}{% endblock %}
|
95
|
-
</body>
|
96
|
-
</html>
|