django-cfg 1.4.75__py3-none-any.whl → 1.4.76__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 (52) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/agents/__init__.py +1 -1
  3. django_cfg/apps/agents/integration/registry.py +1 -1
  4. django_cfg/apps/agents/patterns/content_agents.py +1 -1
  5. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +1 -1
  6. django_cfg/apps/centrifugo/views/dashboard.py +13 -0
  7. django_cfg/apps/centrifugo/views/testing_api.py +74 -15
  8. django_cfg/apps/tasks/views/dashboard.py +4 -4
  9. django_cfg/core/generation/integration_generators/api.py +5 -2
  10. django_cfg/management/commands/check_endpoints.py +1 -1
  11. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +2 -2
  12. django_cfg/modules/django_unfold/callbacks/main.py +27 -25
  13. django_cfg/pyproject.toml +1 -1
  14. django_cfg/static/admin/css/constance.css +44 -0
  15. django_cfg/static/admin/css/dashboard.css +6 -170
  16. django_cfg/static/admin/css/layout.css +21 -0
  17. django_cfg/static/admin/css/tabs.css +95 -0
  18. django_cfg/static/admin/css/theme.css +74 -0
  19. django_cfg/static/admin/js/alpine/activity-tracker.js +55 -0
  20. django_cfg/static/admin/js/alpine/chart.js +101 -0
  21. django_cfg/static/admin/js/alpine/command-modal.js +159 -0
  22. django_cfg/static/admin/js/alpine/commands-panel.js +139 -0
  23. django_cfg/static/admin/js/alpine/commands-section.js +260 -0
  24. django_cfg/static/admin/js/alpine/dashboard-tabs.js +46 -0
  25. django_cfg/static/admin/js/alpine/system-metrics.js +20 -0
  26. django_cfg/static/admin/js/alpine/toggle-section.js +28 -0
  27. django_cfg/static/admin/js/utils.js +60 -0
  28. django_cfg/templates/admin/components/modal.html +1 -1
  29. django_cfg/templates/admin/constance/change_list.html +3 -42
  30. django_cfg/templates/admin/index.html +0 -8
  31. django_cfg/templates/admin/layouts/base_dashboard.html +4 -2
  32. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +104 -502
  33. django_cfg/templates/admin/sections/commands_section.html +374 -451
  34. django_cfg/templates/admin/sections/documentation_section.html +13 -33
  35. django_cfg/templates/admin/snippets/components/activity_tracker.html +27 -49
  36. django_cfg/templates/admin/snippets/components/charts_section.html +8 -74
  37. django_cfg/templates/admin/snippets/components/django_commands.html +94 -181
  38. django_cfg/templates/admin/snippets/components/system_metrics.html +18 -10
  39. django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +2 -2
  40. django_cfg/templates/admin/snippets/tabs/commands_tab.html +48 -0
  41. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +1 -190
  42. django_cfg/templates/admin/snippets/tabs/overview_tab.html +1 -1
  43. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/METADATA +1 -1
  44. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/RECORD +47 -39
  45. django_cfg/static/admin/js/commands.js +0 -171
  46. django_cfg/static/admin/js/dashboard.js +0 -126
  47. django_cfg/templates/admin/components/management_commands.js +0 -375
  48. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +0 -322
  49. django_cfg/templates/admin/snippets/components/recent_activity.html +0 -35
  50. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/WHEEL +0 -0
  51. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/entry_points.txt +0 -0
  52. {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
1
1
  {% load unfold %}
2
2
 
3
3
  <!-- Commands Section -->
4
- <div class="space-y-8">
4
+ <div class="space-y-8" x-data="commandsSection({{ data.commands|length }})">
5
5
  <div class="flex items-center justify-between mb-6">
6
6
  <div>
7
7
  <h1 class="text-2xl font-bold text-font-important-light dark:text-font-important-dark">{{ title|default:"Management Commands" }}</h1>
@@ -10,7 +10,7 @@
10
10
  </p>
11
11
  </div>
12
12
  <div class="text-sm text-font-subtle-light dark:text-font-subtle-dark">
13
- Total: <span id="commands-total" class="font-mono font-semibold text-font-important-light dark:text-font-important-dark">{{ data.commands|length }}</span> commands
13
+ Total: <span x-text="visibleCommands" class="font-mono font-semibold text-font-important-light dark:text-font-important-dark"></span> commands
14
14
  </div>
15
15
  </div>
16
16
 
@@ -20,14 +20,15 @@
20
20
  <span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-base-400 dark:text-base-500">search</span>
21
21
  <input
22
22
  type="text"
23
- id="command-search"
23
+ x-model="searchQuery"
24
+ @input="search()"
24
25
  placeholder="Search commands..."
25
26
  class="w-full pl-10 pr-10 py-2 border border-base-200 dark:border-base-700 rounded-default bg-white dark:bg-base-800 text-font-important-light dark:text-font-important-dark placeholder-font-subtle-light dark:placeholder-font-subtle-dark focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
26
27
  />
27
28
  <button
28
- id="clear-search"
29
- class="hidden absolute right-3 top-1/2 -translate-y-1/2 text-base-400 dark:text-base-500 hover:text-base-600 dark:hover:text-base-300"
30
- onclick="clearSearch()"
29
+ x-show="showClearButton"
30
+ @click="clearSearch()"
31
+ class="absolute right-3 top-1/2 -translate-y-1/2 text-base-400 dark:text-base-500 hover:text-base-600 dark:hover:text-base-300"
31
32
  >
32
33
  <span class="material-symbols-outlined text-lg">close</span>
33
34
  </button>
@@ -41,6 +42,7 @@
41
42
  {% if category_commands %}
42
43
  <div class="category-block bg-gradient-to-br from-white to-base-50 dark:from-base-800 dark:to-base-900 rounded-default shadow-md border border-base-200 dark:border-base-700 overflow-hidden">
43
44
  <div class="p-6 border-b border-base-200 dark:border-base-700 category-toggle cursor-pointer hover:bg-base-100/50 dark:hover:bg-base-900/30 transition-all duration-200"
45
+ @click="toggleCategory('{{ category }}')"
44
46
  data-category="{{ category }}">
45
47
  <div class="w-full flex items-center justify-between">
46
48
  <h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark flex items-center">
@@ -61,17 +63,24 @@
61
63
  </div>
62
64
  </div>
63
65
  </h3>
64
- <span class="material-symbols-outlined text-base-400 dark:text-base-500 toggle-icon transition-transform duration-200 select-none">expand_more</span>
66
+ <span
67
+ class="material-symbols-outlined text-base-400 dark:text-base-500 transition-transform duration-200 select-none"
68
+ x-text="isCategoryExpanded('{{ category }}') ? 'expand_less' : 'expand_more'"
69
+ ></span>
65
70
  </div>
66
71
  </div>
67
- <div class="p-6 category-content bg-base-50/30 dark:bg-base-900/30" data-category="{{ category }}" style="display: none;">
72
+ <div class="p-6 category-content bg-base-50/30 dark:bg-base-900/30"
73
+ data-category="{{ category }}"
74
+ x-show="isCategoryExpanded('{{ category }}')"
75
+ x-cloak>
68
76
  <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
69
77
  {% for cmd in category_commands %}
70
78
  <button type="button"
79
+ @click="confirmCommand('{{ cmd.name }}', '{{ cmd.app }}', '{{ cmd.description|escapejs|default:'-' }}')"
71
80
  class="command-btn command-item text-left p-4 rounded-default border border-base-200/60 dark:border-base-700/60 bg-white dark:bg-base-800 hover:border-base-300 dark:hover:border-base-600 hover:bg-white/80 dark:hover:bg-base-800/80 transition-all duration-200 group"
72
81
  data-command="{{ cmd.name }}"
73
82
  data-app="{{ cmd.app }}"
74
- data-description="{{ cmd.description|default:'' }}">
83
+ data-description="{{ cmd.description|escapejs|default:'-' }}">
75
84
  <div class="flex items-center gap-3">
76
85
  <div class="flex-shrink-0">
77
86
  <span class="material-symbols-outlined text-primary-500 dark:text-primary-400 text-xl group-hover:text-primary-600 dark:group-hover:text-primary-300 transition-colors">terminal</span>
@@ -131,34 +140,95 @@
131
140
  {% endif %}
132
141
 
133
142
  <!-- Confirm Modal -->
134
- <div id="confirm-modal" class="hidden backdrop-blur-xs bg-base-900/80 fixed inset-0 p-4 lg:p-32 z-[1000]">
135
- <div class="bg-white dark:bg-base-800 rounded-default shadow-lg border border-base-200 dark:border-base-700 max-w-3xl mx-auto w-full">
143
+ <div x-show="showConfirmModal"
144
+ @click.self="showConfirmModal = false"
145
+ x-cloak
146
+ x-data="{ modalTab: 'confirm' }"
147
+ class="backdrop-blur-xs bg-base-900/80 fixed inset-0 p-4 lg:p-32 z-[1000]">
148
+ <div class="bg-white dark:bg-base-800 rounded-default shadow-lg border border-base-200 dark:border-base-700 max-w-3xl mx-auto w-full max-h-[80vh] flex flex-col">
136
149
  <div class="px-4 py-3 border-b border-base-200 dark:border-base-700">
137
150
  <h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark flex items-center gap-2">
138
151
  <span class="material-symbols-outlined text-amber-500 dark:text-amber-400">warning</span>
139
152
  Confirm Command Execution
140
153
  </h3>
154
+
155
+ <!-- Tabs -->
156
+ <div class="flex gap-2 mt-4 border-b border-base-200 dark:border-base-700">
157
+ <button type="button"
158
+ @click="modalTab = 'confirm'"
159
+ :class="modalTab === 'confirm' ? 'border-b-2 border-primary-600 text-primary-600' : 'text-font-subtle-light dark:text-font-subtle-dark'"
160
+ class="px-4 py-2 font-medium transition-colors">
161
+ Confirm
162
+ </button>
163
+ <button type="button"
164
+ @click="modalTab = 'help'"
165
+ :class="modalTab === 'help' ? 'border-b-2 border-primary-600 text-primary-600' : 'text-font-subtle-light dark:text-font-subtle-dark'"
166
+ class="px-4 py-2 font-medium transition-colors flex items-center gap-1">
167
+ <span class="material-icons text-sm">help_outline</span>
168
+ Help
169
+ </button>
170
+ </div>
141
171
  </div>
142
- <div class="p-6">
143
- <p class="text-font-default-light dark:text-font-default-dark mb-4">
144
- Are you sure you want to execute the following command?
145
- </p>
146
- <div class="bg-base-100 dark:bg-base-900 rounded-default p-4 border border-base-200 dark:border-base-700">
147
- <div class="flex items-start gap-3">
148
- <span class="material-symbols-outlined text-primary-600 dark:text-primary-400 text-xl">terminal</span>
149
- <div class="flex-1 min-w-0">
150
- <p class="font-mono text-sm font-semibold text-font-important-light dark:text-font-important-dark" id="confirm-command-name"></p>
151
- <p class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-2" id="confirm-command-app"></p>
152
- <p class="text-xs text-font-default-light dark:text-font-default-dark mt-1" id="confirm-command-desc"></p>
172
+
173
+ <div class="flex-1 overflow-y-auto">
174
+ <!-- Confirm Tab -->
175
+ <div x-show="modalTab === 'confirm'" class="p-6">
176
+ <p class="text-font-default-light dark:text-font-default-dark mb-4">
177
+ Are you sure you want to execute the following command?
178
+ </p>
179
+ <div class="bg-base-100 dark:bg-base-900 rounded-default p-4 border border-base-200 dark:border-base-700">
180
+ <div class="flex items-start gap-3">
181
+ <span class="material-symbols-outlined text-primary-600 dark:text-primary-400 text-xl">terminal</span>
182
+ <div class="flex-1 min-w-0">
183
+ <p class="font-mono text-sm font-semibold text-font-important-light dark:text-font-important-dark" x-text="currentCommand"></p>
184
+ <p class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-2" x-show="currentCommandApp" x-text="'from ' + currentCommandApp"></p>
185
+ <p class="text-xs text-font-default-light dark:text-font-default-dark mt-1" x-show="currentCommandDescription" x-text="currentCommandDescription"></p>
186
+ </div>
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- Help Tab -->
192
+ <div x-show="modalTab === 'help'" class="p-6">
193
+ <div class="mb-4">
194
+ <h4 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark mb-2">Command Help</h4>
195
+ <p class="text-sm text-font-subtle-light dark:text-font-subtle-dark mb-4">
196
+ Get detailed information about this command
197
+ </p>
198
+ </div>
199
+
200
+ <div class="bg-base-100 dark:bg-base-900 rounded-default p-4 border border-base-200 dark:border-base-700 mb-4">
201
+ <div class="flex items-start gap-3">
202
+ <span class="material-icons text-primary-600 dark:text-primary-400">info</span>
203
+ <div class="flex-1">
204
+ <p class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Description</p>
205
+ <p class="text-sm text-font-default-light dark:text-font-default-dark" x-text="currentCommandDescription || 'No description available'"></p>
206
+ </div>
207
+ </div>
208
+ </div>
209
+
210
+ <div class="bg-base-100 dark:bg-base-900 rounded-default p-4 border border-base-200 dark:border-base-700">
211
+ <div class="flex items-start gap-3">
212
+ <span class="material-icons text-blue-600 dark:text-blue-400">terminal</span>
213
+ <div class="flex-1">
214
+ <p class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Usage</p>
215
+ <div class="bg-base-900 dark:bg-black text-green-400 p-3 rounded-default font-mono text-xs">
216
+ <span>python manage.py <span x-text="currentCommand"></span> [options]</span>
217
+ </div>
218
+ <p class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-2">
219
+ For detailed help, run: <code class="bg-base-200 dark:bg-base-700 px-2 py-1 rounded">python manage.py help <span x-text="currentCommand"></span></code>
220
+ </p>
221
+ </div>
153
222
  </div>
154
223
  </div>
155
224
  </div>
156
225
  </div>
226
+
157
227
  <div class="px-4 py-3 border-t border-base-200 dark:border-base-800 flex items-center justify-end gap-3">
158
- <button type="button" id="cancel-confirm" class="px-4 py-2 text-font-default-light dark:text-font-default-dark hover:bg-base-100 dark:hover:bg-base-700 rounded-default transition-colors">
228
+ <button type="button" @click="cancelExecution()" class="px-4 py-2 text-font-default-light dark:text-font-default-dark hover:bg-base-100 dark:hover:bg-base-700 rounded-default transition-colors">
159
229
  Cancel
160
230
  </button>
161
- <button type="button" id="execute-confirm" class="px-4 py-2 bg-primary-600 text-white rounded-default hover:bg-primary-700 transition-colors font-medium">
231
+ <button type="button" @click="executeCommand()" class="px-4 py-2 bg-primary-600 text-white rounded-default hover:bg-primary-700 transition-colors font-medium">
162
232
  Execute Command
163
233
  </button>
164
234
  </div>
@@ -166,26 +236,32 @@
166
236
  </div>
167
237
 
168
238
  <!-- Command Output Modal -->
169
- <div id="command-modal" class="hidden backdrop-blur-xs bg-base-900/80 flex flex-col fixed inset-0 p-4 lg:p-32 z-[1000]">
239
+ <div x-show="showOutputModal"
240
+ @click.self="closeOutputModal()"
241
+ x-cloak
242
+ class="backdrop-blur-xs bg-base-900/80 flex flex-col fixed inset-0 p-4 lg:p-32 z-[1000]">
170
243
  <div class="bg-white dark:bg-base-800 flex flex-col max-w-3xl min-h-0 mx-auto overflow-hidden rounded-default shadow-lg w-full">
171
244
  <div class="flex items-center justify-between px-4 py-3 border-b border-base-200 dark:border-base-700">
172
245
  <h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark flex items-center gap-2">
173
246
  <span class="material-symbols-outlined text-primary-600 dark:text-primary-400">terminal</span>
174
- <span id="modal-command-name" class="font-mono"></span>
247
+ <span class="font-mono" x-text="currentCommand"></span>
175
248
  </h3>
176
- <button type="button" id="close-modal" class="text-base-400 dark:text-base-500 hover:text-base-600 dark:hover:text-base-300 transition-colors">
249
+ <button type="button" @click="closeOutputModal()" class="text-base-400 dark:text-base-500 hover:text-base-600 dark:hover:text-base-300 transition-colors">
177
250
  <span class="material-symbols-outlined">close</span>
178
251
  </button>
179
252
  </div>
180
- <div class="grow overflow-y-auto overflow-x-hidden w-full" data-simplebar id="output-container">
253
+ <div class="grow overflow-y-auto overflow-x-hidden w-full" x-ref="commandOutput">
181
254
  <div class="p-4">
182
- <pre id="command-output" class="bg-base-900 dark:bg-black text-green-400 p-4 rounded-default font-mono text-xs leading-relaxed whitespace-pre overflow-x-auto"></pre>
255
+ <pre x-text="commandOutput" class="bg-base-900 dark:bg-black text-green-400 p-4 rounded-default font-mono text-xs leading-relaxed whitespace-pre overflow-x-auto"></pre>
183
256
  </div>
184
257
  </div>
185
258
  <div class="px-4 py-3 border-t border-base-200 dark:border-base-700">
186
259
  <div class="flex items-center justify-between">
187
- <div id="command-status" class="text-sm font-medium"></div>
188
- <button type="button" id="copy-output" class="px-4 py-2 bg-primary-600 text-white rounded-default hover:bg-primary-700 transition-colors flex items-center gap-2 font-medium">
260
+ <div class="flex items-center gap-2">
261
+ <div :class="statusClass" class="w-3 h-3 rounded-full"></div>
262
+ <span class="text-sm font-medium" x-text="statusText"></span>
263
+ </div>
264
+ <button type="button" @click="copyOutput()" class="px-4 py-2 bg-primary-600 text-white rounded-default hover:bg-primary-700 transition-colors flex items-center gap-2 font-medium">
189
265
  <span class="material-symbols-outlined text-base">content_copy</span>
190
266
  Copy Output
191
267
  </button>
@@ -193,434 +269,281 @@
193
269
  </div>
194
270
  </div>
195
271
  </div>
196
- </div>
197
-
198
- <!-- JavaScript for Commands Section -->
199
- <script>
200
- document.addEventListener('DOMContentLoaded', function() {
201
- // Category toggle functionality
202
- const categoryToggles = document.querySelectorAll('.category-toggle');
203
- categoryToggles.forEach(toggle => {
204
- toggle.addEventListener('click', function() {
205
- const category = this.dataset.category;
206
- const content = document.querySelector(`.category-content[data-category="${category}"]`);
207
- const icon = this.querySelector('.toggle-icon');
208
-
209
- if (content.style.display === 'none' || !content.style.display) {
210
- content.style.display = 'block';
211
- icon.textContent = 'expand_less';
212
- } else {
213
- content.style.display = 'none';
214
- icon.textContent = 'expand_more';
215
- }
216
- });
217
-
218
- // Initialize - start collapsed
219
- const category = toggle.dataset.category;
220
- const content = document.querySelector(`.category-content[data-category="${category}"]`);
221
- if (content) {
222
- content.style.display = 'none';
223
- }
224
- });
225
-
226
- // Command click handlers - show confirm first
227
- const commandButtons = document.querySelectorAll('.command-btn');
228
- commandButtons.forEach(btn => {
229
- btn.addEventListener('click', function() {
230
- const commandName = this.dataset.command;
231
- const commandApp = this.dataset.app || '';
232
- const commandDesc = this.dataset.description || '';
233
- showConfirmModal(commandName, commandApp, commandDesc);
234
- });
235
- });
236
-
237
- // Confirm modal handlers
238
- const confirmModal = document.getElementById('confirm-modal');
239
- const cancelConfirm = document.getElementById('cancel-confirm');
240
- const executeConfirm = document.getElementById('execute-confirm');
241
-
242
- if (cancelConfirm) {
243
- cancelConfirm.addEventListener('click', function() {
244
- confirmModal.classList.add('hidden');
245
- });
246
- }
247
-
248
- if (executeConfirm) {
249
- executeConfirm.addEventListener('click', function() {
250
- const commandName = document.getElementById('confirm-command-name').textContent;
251
- confirmModal.classList.add('hidden');
252
- executeCommand(commandName);
253
- });
254
- }
255
272
 
256
- // Close confirm modal on background click
257
- if (confirmModal) {
258
- confirmModal.addEventListener('click', function(e) {
259
- if (e.target === confirmModal) {
260
- confirmModal.classList.add('hidden');
261
- }
262
- });
263
- }
273
+ <!-- Load Alpine component -->
274
+ {% load static %}
275
+ <script src="{% static 'admin/js/alpine/commands-section.js' %}"></script>
264
276
 
265
- // Output Modal close handlers
266
- const outputModal = document.getElementById('command-modal');
267
- const closeBtn = document.getElementById('close-modal');
268
-
269
- if (closeBtn) {
270
- closeBtn.addEventListener('click', function() {
271
- outputModal.classList.add('hidden');
272
- });
273
- }
274
-
275
- // Close output modal on background click
276
- if (outputModal) {
277
- outputModal.addEventListener('click', function(e) {
278
- if (e.target === outputModal) {
279
- outputModal.classList.add('hidden');
280
- }
281
- });
282
- }
283
-
284
- // Copy output functionality
285
- const copyBtn = document.getElementById('copy-output');
286
- if (copyBtn) {
287
- copyBtn.addEventListener('click', function() {
288
- const output = document.getElementById('command-output');
289
- if (output) {
290
- navigator.clipboard.writeText(output.textContent).then(function() {
291
- const originalText = copyBtn.innerHTML;
292
- copyBtn.innerHTML = '<span class="material-symbols-outlined mr-2">check</span>Copied!';
293
- setTimeout(function() {
294
- copyBtn.innerHTML = originalText;
295
- }, 2000);
296
- }).catch(function(err) {
297
- console.error('Could not copy text: ', err);
298
- });
299
- }
300
- });
301
- }
302
-
303
- // Escape key to close modals
304
- document.addEventListener('keydown', function(e) {
305
- if (e.key === 'Escape') {
306
- if (confirmModal && !confirmModal.classList.contains('hidden')) {
307
- confirmModal.classList.add('hidden');
308
- }
309
- if (outputModal && !outputModal.classList.contains('hidden')) {
310
- outputModal.classList.add('hidden');
311
- }
312
- }
313
- });
314
-
315
- // Search functionality
316
- const searchInput = document.getElementById('command-search');
317
- const clearSearchBtn = document.getElementById('clear-search');
318
-
319
- if (searchInput) {
320
- searchInput.addEventListener('input', function(e) {
321
- const query = e.target.value.toLowerCase().trim();
322
- filterCommands(query);
323
-
324
- // Show/hide clear button
325
- if (query) {
326
- clearSearchBtn.classList.remove('hidden');
327
- } else {
328
- clearSearchBtn.classList.add('hidden');
329
- }
330
- });
331
- }
332
- });
333
-
334
- // Search/filter commands
335
- function filterCommands(query) {
336
- const categoryBlocks = document.querySelectorAll('.category-block');
337
- let totalVisible = 0;
338
-
339
- categoryBlocks.forEach(block => {
340
- const commands = block.querySelectorAll('.command-item');
341
- const categoryCount = block.querySelector('.category-count');
342
- let visibleCount = 0;
343
-
344
- commands.forEach(cmd => {
345
- const commandName = cmd.querySelector('.command-name').textContent.toLowerCase();
346
- const commandApp = cmd.dataset.app ? cmd.dataset.app.toLowerCase() : '';
347
- const commandDesc = cmd.dataset.description ? cmd.dataset.description.toLowerCase() : '';
348
-
349
- if (!query || commandName.includes(query) || commandApp.includes(query) || commandDesc.includes(query)) {
350
- cmd.style.display = 'block';
351
- visibleCount++;
352
- totalVisible++;
353
- } else {
354
- cmd.style.display = 'none';
355
- }
356
- });
357
-
358
- // Update category count
359
- if (categoryCount) {
360
- categoryCount.textContent = `(${visibleCount})`;
361
- }
277
+ <!-- Documentation Section (Collapsible) -->
278
+ <div class="mt-12" x-data="{ docsExpanded: false }">
279
+ <div class="bg-gradient-to-br from-white to-base-50 dark:from-base-800 dark:to-base-900 rounded-lg shadow-md border border-base-200 dark:border-base-700 overflow-hidden">
280
+ <!-- Header (Clickable) -->
281
+ <div class="p-6 cursor-pointer hover:bg-base-100/50 dark:hover:bg-base-900/30 transition-all duration-200"
282
+ @click="docsExpanded = !docsExpanded">
283
+ <div class="flex items-center justify-between">
284
+ <div class="flex items-center">
285
+ <div class="w-10 h-10 rounded-lg bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center mr-3">
286
+ <span class="material-icons text-blue-600 dark:text-blue-400">menu_book</span>
287
+ </div>
288
+ <div>
289
+ <h2 class="text-xl font-bold text-font-important-light dark:text-font-important-dark">
290
+ Commands Documentation
291
+ </h2>
292
+ <p class="text-sm text-font-subtle-light dark:text-font-subtle-dark mt-1">
293
+ Complete guide to Django management commands
294
+ </p>
295
+ </div>
296
+ </div>
297
+ <span class="material-icons text-base-400 dark:text-base-500 transition-transform duration-200"
298
+ x-text="docsExpanded ? 'expand_less' : 'expand_more'"></span>
299
+ </div>
300
+ </div>
362
301
 
363
- // Hide category if no visible commands
364
- if (visibleCount === 0) {
365
- block.style.display = 'none';
366
- } else {
367
- block.style.display = 'block';
302
+ <!-- Content (Collapsible) -->
303
+ <div class="border-t border-base-200 dark:border-base-700"
304
+ x-show="docsExpanded"
305
+ x-collapse>
306
+ <div class="p-6 bg-base-50/30 dark:bg-base-900/30">
307
+ {% if documentation_content %}
308
+ <!-- Rendered Markdown Content with Collapsible Sections -->
309
+ <div class="documentation-content" x-data="documentationCollapse()">
310
+ {{ documentation_content|safe }}
311
+ </div>
312
+ {% else %}
313
+ <!-- Fallback: Basic documentation -->
314
+ <div class="documentation-content">
315
+ <h2>Django Management Commands</h2>
316
+ <p>Django management commands provide a command-line interface for administrative tasks.</p>
317
+
318
+ <h3>Common Commands</h3>
319
+ <ul>
320
+ <li><code>migrate</code> - Apply database migrations</li>
321
+ <li><code>makemigrations</code> - Create new migrations</li>
322
+ <li><code>runserver</code> - Start development server</li>
323
+ <li><code>createsuperuser</code> - Create admin user</li>
324
+ <li><code>collectstatic</code> - Collect static files</li>
325
+ </ul>
326
+
327
+ <h3>Getting Help</h3>
328
+ <p>To get help for any command, use:</p>
329
+ <pre><code>python manage.py help &lt;command_name&gt;</code></pre>
330
+
331
+ <h3>Custom Commands</h3>
332
+ <p>You can create custom management commands by adding them to <code>management/commands/</code> directory in your Django app.</p>
333
+ </div>
334
+ {% endif %}
335
+ </div>
336
+ </div>
337
+ </div>
338
+ </div>
368
339
 
369
- // Auto-expand categories when searching
370
- if (query) {
371
- const content = block.querySelector('.category-content');
372
- const icon = block.querySelector('.toggle-icon');
373
- if (content && icon) {
374
- content.style.display = 'block';
375
- icon.textContent = 'expand_less';
340
+ <!-- Documentation Collapse Script -->
341
+ <script>
342
+ function documentationCollapse() {
343
+ return {
344
+ sections: {},
345
+ init() {
346
+ // Find all h2 elements and make them collapsible
347
+ this.$nextTick(() => {
348
+ const h2Elements = this.$el.querySelectorAll('h2');
349
+ h2Elements.forEach((h2, index) => {
350
+ const sectionId = `section-${index}`;
351
+ this.sections[sectionId] = false; // collapsed by default
352
+
353
+ // Wrap h2 in clickable div
354
+ const wrapper = document.createElement('div');
355
+ wrapper.className = 'doc-section-header';
356
+ wrapper.setAttribute('x-on:click', `sections['${sectionId}'] = !sections['${sectionId}']`);
357
+
358
+ const icon = document.createElement('span');
359
+ icon.className = 'material-icons doc-section-icon';
360
+ icon.setAttribute('x-text', `sections['${sectionId}'] ? 'expand_less' : 'expand_more'`);
361
+
362
+ h2.parentNode.insertBefore(wrapper, h2);
363
+ wrapper.appendChild(icon);
364
+ wrapper.appendChild(h2);
365
+
366
+ // Wrap content until next h2
367
+ const contentWrapper = document.createElement('div');
368
+ contentWrapper.className = 'doc-section-content';
369
+ contentWrapper.setAttribute('x-show', `sections['${sectionId}']`);
370
+ contentWrapper.setAttribute('x-collapse', '');
371
+
372
+ let nextElement = wrapper.nextElementSibling;
373
+ const elementsToMove = [];
374
+
375
+ while (nextElement && nextElement.tagName !== 'H2') {
376
+ elementsToMove.push(nextElement);
377
+ nextElement = nextElement.nextElementSibling;
378
+ }
379
+
380
+ wrapper.parentNode.insertBefore(contentWrapper, wrapper.nextElementSibling);
381
+ elementsToMove.forEach(el => contentWrapper.appendChild(el));
382
+ });
383
+ });
376
384
  }
377
- }
385
+ };
378
386
  }
379
- });
380
-
381
- // Update total commands count
382
- const totalCounter = document.getElementById('commands-total');
383
- if (totalCounter) {
384
- totalCounter.textContent = totalVisible;
385
- }
386
- }
387
-
388
- // Clear search
389
- function clearSearch() {
390
- const searchInput = document.getElementById('command-search');
391
- const clearBtn = document.getElementById('clear-search');
392
- const categoryBlocks = document.querySelectorAll('.category-block');
393
- const totalCounter = document.getElementById('commands-total');
394
-
395
- // Clear input
396
- if (searchInput) {
397
- searchInput.value = '';
398
- }
399
-
400
- // Hide clear button
401
- if (clearBtn) {
402
- clearBtn.classList.add('hidden');
403
- }
387
+ </script>
404
388
 
405
- // Reset all blocks
406
- categoryBlocks.forEach(block => {
407
- block.style.display = 'block';
408
- const commands = block.querySelectorAll('.command-item');
409
- const categoryCount = block.querySelector('.category-count');
410
-
411
- // Show all commands
412
- commands.forEach(cmd => {
413
- cmd.style.display = 'block';
414
- });
415
-
416
- // Reset category count
417
- if (categoryCount) {
418
- const originalCount = commands.length;
419
- categoryCount.textContent = `(${originalCount})`;
389
+ <!-- Documentation Styles -->
390
+ <style>
391
+ .documentation-content {
392
+ color: #374151;
393
+ line-height: 1.75;
420
394
  }
421
-
422
- // Collapse categories
423
- const content = block.querySelector('.category-content');
424
- const icon = block.querySelector('.toggle-icon');
425
- if (content && icon) {
426
- content.style.display = 'none';
427
- icon.textContent = 'expand_more';
428
- }
429
- });
430
-
431
- // Reset total
432
- if (totalCounter) {
433
- const allCommands = document.querySelectorAll('.command-item');
434
- totalCounter.textContent = allCommands.length;
435
- }
436
- }
437
-
438
- // Show confirm modal
439
- function showConfirmModal(commandName, commandApp, commandDesc) {
440
- const confirmModal = document.getElementById('confirm-modal');
441
- const confirmCommandName = document.getElementById('confirm-command-name');
442
- const confirmCommandApp = document.getElementById('confirm-command-app');
443
- const confirmCommandDesc = document.getElementById('confirm-command-desc');
444
-
445
- if (!confirmModal || !confirmCommandName) {
446
- console.error('Confirm modal elements not found');
447
- return;
448
- }
449
-
450
- confirmCommandName.textContent = commandName;
451
- confirmCommandApp.textContent = commandApp ? `from ${commandApp}` : '';
452
- confirmCommandDesc.textContent = commandDesc || '';
453
-
454
- confirmModal.classList.remove('hidden');
455
- }
456
-
457
- // Execute command function with SSE streaming
458
- function executeCommand(commandName) {
459
- const modal = document.getElementById('command-modal');
460
- const modalCommandName = document.getElementById('modal-command-name');
461
- const commandOutput = document.getElementById('command-output');
462
- const commandStatus = document.getElementById('command-status');
463
-
464
- if (!modal || !modalCommandName || !commandOutput || !commandStatus) {
465
- console.error('Command modal elements not found');
466
- return;
467
- }
468
-
469
- // Show modal and initialize
470
- modalCommandName.textContent = commandName;
471
- commandOutput.innerHTML = '';
472
- commandStatus.textContent = 'Executing...';
473
- commandStatus.className = 'text-sm text-primary-600 dark:text-primary-400';
474
- modal.classList.remove('hidden');
475
-
476
- // Execute command via API with SSE streaming
477
- fetch('/cfg/commands/execute/', {
478
- method: 'POST',
479
- headers: {
480
- 'Content-Type': 'application/json',
481
- 'X-CSRFToken': getCookie('csrftoken')
482
- },
483
- body: JSON.stringify({
484
- command: commandName,
485
- args: [],
486
- options: {}
487
- })
488
- })
489
- .then(response => {
490
- if (!response.ok) {
491
- throw new Error(`HTTP error! status: ${response.status}`);
395
+ html.dark .documentation-content {
396
+ color: #d1d5db;
492
397
  }
493
-
494
- const reader = response.body.getReader();
495
- const decoder = new TextDecoder();
496
-
497
- function readStream() {
498
- return reader.read().then(({done, value}) => {
499
- if (done) return;
500
-
501
- const chunk = decoder.decode(value);
502
- const lines = chunk.split('\n');
503
-
504
- lines.forEach(line => {
505
- if (line.startsWith('data: ')) {
506
- try {
507
- const data = JSON.parse(line.slice(6));
508
- handleCommandData(data);
509
- } catch (e) {
510
- console.error('Error parsing command data:', e, line);
511
- }
512
- }
513
- });
514
-
515
- return readStream();
516
- });
398
+ .documentation-content h1 {
399
+ font-size: 2em;
400
+ font-weight: 700;
401
+ margin-top: 1.5em;
402
+ margin-bottom: 0.75em;
403
+ color: #111827;
517
404
  }
405
+ html.dark .documentation-content h1 {
406
+ color: #f9fafb;
407
+ }
408
+ .doc-section-header {
409
+ display: flex;
410
+ align-items: center;
411
+ cursor: pointer;
412
+ padding: 0.75em 0;
413
+ margin-top: 1.25em;
414
+ border-bottom: 1px solid #e5e7eb;
415
+ transition: all 0.2s ease;
416
+ }
417
+ .doc-section-header:hover {
418
+ background-color: rgba(0, 0, 0, 0.02);
419
+ padding-left: 0.5em;
420
+ margin-left: -0.5em;
421
+ padding-right: 0.5em;
422
+ margin-right: -0.5em;
423
+ border-radius: 0.5em;
424
+ }
425
+ html.dark .doc-section-header {
426
+ border-bottom-color: #374151;
427
+ }
428
+ html.dark .doc-section-header:hover {
429
+ background-color: rgba(255, 255, 255, 0.05);
430
+ }
431
+ .doc-section-icon {
432
+ color: #6b7280;
433
+ font-size: 1.25em;
434
+ margin-right: 0.5em;
435
+ transition: transform 0.2s ease;
436
+ }
437
+ html.dark .doc-section-icon {
438
+ color: #9ca3af;
439
+ }
440
+ .doc-section-header h2 {
441
+ font-size: 1.5em;
442
+ font-weight: 600;
443
+ margin: 0;
444
+ color: #1f2937;
445
+ border: none;
446
+ padding: 0;
447
+ }
448
+ html.dark .doc-section-header h2 {
449
+ color: #e5e7eb;
450
+ }
451
+ .doc-section-content {
452
+ padding-left: 2.25em;
453
+ margin-bottom: 1em;
454
+ }
455
+ .documentation-content h3 {
456
+ font-size: 1.25em;
457
+ font-weight: 600;
458
+ margin-top: 1em;
459
+ margin-bottom: 0.5em;
460
+ color: #374151;
461
+ }
462
+ html.dark .documentation-content h3 {
463
+ color: #d1d5db;
464
+ }
465
+ .documentation-content h4 {
466
+ font-size: 1.1em;
467
+ font-weight: 600;
468
+ margin-top: 0.875em;
469
+ margin-bottom: 0.5em;
470
+ color: #4b5563;
471
+ }
472
+ html.dark .documentation-content h4 {
473
+ color: #9ca3af;
474
+ }
475
+ .documentation-content p {
476
+ margin-bottom: 1em;
477
+ }
478
+ .documentation-content ul, .documentation-content ol {
479
+ margin-bottom: 1em;
480
+ padding-left: 1.5em;
481
+ }
482
+ .documentation-content li {
483
+ margin-bottom: 0.5em;
484
+ }
485
+ .documentation-content code {
486
+ background-color: #f3f4f6;
487
+ color: #dc2626;
488
+ padding: 0.125em 0.375em;
489
+ border-radius: 0.25em;
490
+ font-family: 'Courier New', monospace;
491
+ font-size: 0.875em;
492
+ }
493
+ html.dark .documentation-content code {
494
+ background-color: #1f2937;
495
+ color: #f87171;
496
+ }
497
+ .documentation-content pre {
498
+ background-color: #1f2937;
499
+ color: #e5e7eb;
500
+ padding: 1em;
501
+ border-radius: 0.5em;
502
+ overflow-x: auto;
503
+ margin-bottom: 1em;
504
+ }
505
+ html.dark .documentation-content pre {
506
+ background-color: #111827;
507
+ }
508
+ .documentation-content pre code {
509
+ background-color: transparent;
510
+ color: #10b981;
511
+ padding: 0;
512
+ }
513
+ .documentation-content a {
514
+ color: #2563eb;
515
+ text-decoration: underline;
516
+ }
517
+ html.dark .documentation-content a {
518
+ color: #60a5fa;
519
+ }
520
+ .documentation-content strong {
521
+ font-weight: 600;
522
+ color: #111827;
523
+ }
524
+ html.dark .documentation-content strong {
525
+ color: #f9fafb;
526
+ }
527
+ .documentation-content table {
528
+ width: 100%;
529
+ border-collapse: collapse;
530
+ margin-bottom: 1em;
531
+ }
532
+ .documentation-content th, .documentation-content td {
533
+ border: 1px solid #e5e7eb;
534
+ padding: 0.5em;
535
+ text-align: left;
536
+ }
537
+ html.dark .documentation-content th, html.dark .documentation-content td {
538
+ border-color: #374151;
539
+ }
540
+ .documentation-content th {
541
+ background-color: #f3f4f6;
542
+ font-weight: 600;
543
+ }
544
+ html.dark .documentation-content th {
545
+ background-color: #1f2937;
546
+ }
547
+ </style>
548
+ </div>
518
549
 
519
- return readStream();
520
- })
521
- .catch(error => {
522
- console.error('Error executing command:', error);
523
- addLogLine(commandOutput, `\n❌ Error: ${error.message}`, 'error');
524
- commandStatus.textContent = '❌ Error';
525
- commandStatus.className = 'text-sm text-red-500 dark:text-red-400';
526
- });
527
- }
528
-
529
- // Handle SSE command data
530
- function handleCommandData(data) {
531
- const output = document.getElementById('command-output');
532
- const status = document.getElementById('command-status');
533
-
534
- if (!output || !status) return;
535
-
536
- switch (data.type) {
537
- case 'start':
538
- output.innerHTML = '';
539
- addLogLine(output, `🚀 Starting command: ${data.command}`, 'info');
540
- const args = (data.args && Array.isArray(data.args)) ? data.args.join(' ') : 'none';
541
- addLogLine(output, `📝 Arguments: ${args}`, 'info');
542
- addLogLine(output, '', 'spacer');
543
- status.textContent = 'Executing...';
544
- status.className = 'text-sm text-primary-600 dark:text-primary-400';
545
- break;
546
- case 'output':
547
- addLogLine(output, data.line, 'output');
548
- scrollToBottom(document.getElementById('output-container'));
549
- break;
550
- case 'complete':
551
- const success = data.return_code === 0;
552
- status.textContent = success ? '✅ Success' : '❌ Failed';
553
- status.className = success
554
- ? 'text-sm text-green-500 dark:text-green-400'
555
- : 'text-sm text-red-500 dark:text-red-400';
556
-
557
- addLogLine(output, '', 'spacer');
558
- let completionMessage = `${success ? '✅' : '❌'} Command completed with exit code: ${data.return_code}`;
559
- if (data.execution_time) {
560
- completionMessage += ` (${data.execution_time}s)`;
561
- }
562
- addLogLine(output, completionMessage, success ? 'success' : 'error');
563
- scrollToBottom(document.getElementById('output-container'));
564
- break;
565
- case 'error':
566
- const errorMsg = data.message || data.error || 'Unknown error';
567
- addLogLine(output, `❌ ${errorMsg}`, 'error');
568
- status.textContent = '❌ Error';
569
- status.className = 'text-sm text-red-500 dark:text-red-400';
570
- scrollToBottom(document.getElementById('output-container'));
571
- break;
572
- }
573
- }
574
-
575
- // Add log line to output
576
- function addLogLine(container, text, type = 'output') {
577
- const line = document.createElement('div');
578
- line.className = 'log-line font-mono';
579
-
580
- switch (type) {
581
- case 'info':
582
- line.className += ' text-blue-500 dark:text-blue-400';
583
- break;
584
- case 'success':
585
- line.className += ' text-green-500 dark:text-green-400 font-semibold';
586
- break;
587
- case 'error':
588
- line.className += ' text-red-500 dark:text-red-400 font-semibold';
589
- break;
590
- case 'spacer':
591
- line.style.height = '0.5em';
592
- line.innerHTML = '&nbsp;';
593
- break;
594
- default:
595
- line.className += ' text-green-400 dark:text-green-400';
596
- }
597
-
598
- if (type !== 'spacer') {
599
- line.textContent = text;
600
- }
601
- container.appendChild(line);
602
- }
603
-
604
- // Scroll to bottom helper
605
- function scrollToBottom(element) {
606
- if (element) {
607
- element.scrollTop = element.scrollHeight;
608
- }
609
- }
610
-
611
- // Get CSRF token
612
- function getCookie(name) {
613
- let cookieValue = null;
614
- if (document.cookie && document.cookie !== '') {
615
- const cookies = document.cookie.split(';');
616
- for (let i = 0; i < cookies.length; i++) {
617
- const cookie = cookies[i].trim();
618
- if (cookie.substring(0, name.length + 1) === (name + '=')) {
619
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
620
- break;
621
- }
622
- }
623
- }
624
- return cookieValue;
625
- }
626
- </script>