django-cfg 1.4.74__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 (54) 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/api/health/drf_views.py +27 -0
  6. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +1 -1
  7. django_cfg/apps/centrifugo/views/dashboard.py +13 -0
  8. django_cfg/apps/centrifugo/views/testing_api.py +74 -15
  9. django_cfg/apps/tasks/views/dashboard.py +4 -4
  10. django_cfg/core/generation/integration_generators/api.py +9 -0
  11. django_cfg/management/commands/check_endpoints.py +1 -1
  12. django_cfg/middleware/authentication.py +27 -0
  13. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +2 -2
  14. django_cfg/modules/django_unfold/callbacks/main.py +27 -25
  15. django_cfg/pyproject.toml +1 -1
  16. django_cfg/static/admin/css/constance.css +44 -0
  17. django_cfg/static/admin/css/dashboard.css +6 -170
  18. django_cfg/static/admin/css/layout.css +21 -0
  19. django_cfg/static/admin/css/tabs.css +95 -0
  20. django_cfg/static/admin/css/theme.css +74 -0
  21. django_cfg/static/admin/js/alpine/activity-tracker.js +55 -0
  22. django_cfg/static/admin/js/alpine/chart.js +101 -0
  23. django_cfg/static/admin/js/alpine/command-modal.js +159 -0
  24. django_cfg/static/admin/js/alpine/commands-panel.js +139 -0
  25. django_cfg/static/admin/js/alpine/commands-section.js +260 -0
  26. django_cfg/static/admin/js/alpine/dashboard-tabs.js +46 -0
  27. django_cfg/static/admin/js/alpine/system-metrics.js +20 -0
  28. django_cfg/static/admin/js/alpine/toggle-section.js +28 -0
  29. django_cfg/static/admin/js/utils.js +60 -0
  30. django_cfg/templates/admin/components/modal.html +1 -1
  31. django_cfg/templates/admin/constance/change_list.html +3 -42
  32. django_cfg/templates/admin/index.html +0 -8
  33. django_cfg/templates/admin/layouts/base_dashboard.html +4 -2
  34. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +104 -502
  35. django_cfg/templates/admin/sections/commands_section.html +374 -451
  36. django_cfg/templates/admin/sections/documentation_section.html +13 -33
  37. django_cfg/templates/admin/snippets/components/activity_tracker.html +27 -49
  38. django_cfg/templates/admin/snippets/components/charts_section.html +8 -74
  39. django_cfg/templates/admin/snippets/components/django_commands.html +94 -181
  40. django_cfg/templates/admin/snippets/components/system_metrics.html +18 -10
  41. django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +2 -2
  42. django_cfg/templates/admin/snippets/tabs/commands_tab.html +48 -0
  43. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +1 -190
  44. django_cfg/templates/admin/snippets/tabs/overview_tab.html +1 -1
  45. {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/METADATA +1 -1
  46. {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/RECORD +49 -41
  47. django_cfg/static/admin/js/commands.js +0 -171
  48. django_cfg/static/admin/js/dashboard.js +0 -126
  49. django_cfg/templates/admin/components/management_commands.js +0 -375
  50. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +0 -322
  51. django_cfg/templates/admin/snippets/components/recent_activity.html +0 -35
  52. {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/WHEEL +0 -0
  53. {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/entry_points.txt +0 -0
  54. {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/licenses/LICENSE +0 -0
@@ -6,425 +6,29 @@
6
6
  {{ block.super }}
7
7
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
8
8
  <!-- Removed Tailwind CDN - using CSS-only solution -->
9
-
10
- <script>
11
- // Dashboard tabs functionality
12
- document.addEventListener('DOMContentLoaded', function() {
13
- const tabs = document.querySelectorAll('#dashboard-tabs button');
14
- const contents = document.querySelectorAll('.tab-content');
15
-
16
- if (!tabs.length || !contents.length) return;
17
-
18
- // Tab names for URL hash
19
- const tabNames = ['overview', 'zones', 'users', 'system', 'stats', 'app-stats', 'commands', 'documentation', 'widgets'];
20
-
21
- // Global function for tab switching
22
- window.switchTab = function(idx, updateHash = true) {
23
- tabs.forEach((t, i) => {
24
- if (i === idx) {
25
- // Active tab
26
- t.classList.add('active');
27
- } else {
28
- // Inactive tab
29
- t.classList.remove('active');
30
- }
31
- });
32
-
33
- contents.forEach((content, i) => {
34
- content.style.display = i === idx ? 'block' : 'none';
35
- content.classList.toggle('active', i === idx);
36
- });
37
-
38
- // Update URL hash
39
- if (updateHash && tabNames[idx]) {
40
- history.replaceState(null, null, '#' + tabNames[idx]);
41
- }
42
- };
43
-
44
- // Function to get tab index from hash
45
- function getTabFromHash() {
46
- const hash = window.location.hash.substring(1); // Remove #
47
- const tabIndex = tabNames.indexOf(hash);
48
- return tabIndex >= 0 ? tabIndex : 0; // Default to first tab
49
- }
50
-
51
- // Add click handlers
52
- tabs.forEach((tab, idx) => {
53
- tab.onclick = function(e) {
54
- e.preventDefault();
55
- switchTab(idx, true);
56
- };
57
- });
58
-
59
- // Handle browser back/forward buttons
60
- window.addEventListener('hashchange', function() {
61
- const tabIndex = getTabFromHash();
62
- switchTab(tabIndex, false); // Don't update hash to avoid loop
63
- });
64
-
65
- // Activate tab based on URL hash or default to first tab
66
- const initialTab = getTabFromHash();
67
- switchTab(initialTab, false);
68
- });
69
-
70
- // Global functions for components
71
- window.toggleCategory = function(category) {
72
- const content = document.getElementById(`content-${category}`);
73
- const icon = document.getElementById(`icon-${category}`);
74
-
75
- if (!content || !icon) return;
76
-
77
- if (content.style.display === 'none' || content.style.display === '') {
78
- content.style.display = 'block';
79
- icon.style.transform = 'rotate(0deg)';
80
- icon.textContent = 'expand_more';
81
- } else {
82
- content.style.display = 'none';
83
- icon.style.transform = 'rotate(-90deg)';
84
- icon.textContent = 'expand_less';
85
- }
86
- };
87
9
 
88
- // Command execution functions
89
- window.copyToClipboard = function(text) {
90
- navigator.clipboard.writeText(text).then(function() {
91
- const button = event.target.closest('button');
92
- const originalText = button.innerHTML;
93
- button.innerHTML = '<span class="material-icons text-xs mr-1">check</span>Copied';
94
- button.classList.remove('bg-gray-100', 'dark:bg-gray-700', 'hover:bg-gray-200', 'dark:hover:bg-gray-600');
95
- button.classList.add('bg-green-600', 'hover:bg-green-700', 'dark:bg-green-500', 'dark:hover:bg-green-600', 'text-white');
96
-
97
- setTimeout(function() {
98
- button.innerHTML = originalText;
99
- button.classList.remove('bg-green-600', 'hover:bg-green-700', 'dark:bg-green-500', 'dark:hover:bg-green-600', 'text-white');
100
- button.classList.add('bg-gray-100', 'dark:bg-gray-700', 'hover:bg-gray-200', 'dark:hover:bg-gray-600');
101
- }, 2000);
102
- }).catch(function(err) {
103
- console.error('Could not copy text: ', err);
104
- });
105
- };
10
+ <!-- Alpine.js Components - Load before Alpine.js -->
11
+ {% load static %}
12
+ <script src="{% static 'admin/js/utils.js' %}"></script>
13
+ <script src="{% static 'admin/js/alpine/dashboard-tabs.js' %}"></script>
106
14
 
107
- // No JS needed - using pure CSS solution
15
+ <!-- Alpine.js Collapse Plugin (must load before Alpine.js) -->
16
+ <script src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
108
17
 
109
- window.executeCommand = function(commandName) {
110
- const modal = document.getElementById('commandModal');
111
- const commandNameEl = document.getElementById('commandName');
112
- const commandOutput = document.getElementById('commandOutput');
113
- const commandStatus = document.getElementById('commandStatus');
114
-
115
- if (!modal || !commandNameEl || !commandOutput || !commandStatus) {
116
- console.error('Command modal elements not found');
117
- return;
118
- }
18
+ <!-- Alpine.js - Load last with defer -->
19
+ <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
119
20
 
120
- commandNameEl.textContent = commandName;
121
- commandOutput.textContent = '';
122
- commandStatus.innerHTML = '<div class="w-3 h-3 bg-yellow-500 rounded-full mr-2 animate-pulse"></div><span class="text-sm font-medium text-gray-700 dark:text-gray-300">Executing...</span>';
123
- modal.classList.remove('hidden');
124
-
125
- fetch('/cfg/commands/execute/', {
126
- method: 'POST',
127
- headers: {
128
- 'Content-Type': 'application/json',
129
- 'X-CSRFToken': getCookie('csrftoken')
130
- },
131
- body: JSON.stringify({
132
- command: commandName,
133
- args: [],
134
- options: {}
135
- })
136
- })
137
- .then(response => {
138
- if (!response.ok) {
139
- throw new Error(`HTTP error! status: ${response.status}`);
140
- }
141
-
142
- const reader = response.body.getReader();
143
- const decoder = new TextDecoder();
144
-
145
- function readStream() {
146
- return reader.read().then(({done, value}) => {
147
- if (done) return;
148
-
149
- const chunk = decoder.decode(value);
150
- const lines = chunk.split('\n');
151
-
152
- lines.forEach(line => {
153
- if (line.startsWith('data: ')) {
154
- try {
155
- const data = JSON.parse(line.slice(6));
156
- handleCommandData(data);
157
- } catch (e) {
158
- console.error('Error parsing command data:', e);
159
- }
160
- }
161
- });
162
-
163
- return readStream();
164
- });
165
- }
166
-
167
- return readStream();
168
- })
169
- .catch(error => {
170
- console.error('Error executing command:', error);
171
- commandOutput.textContent += `\n❌ Error: ${error.message}`;
172
- commandStatus.innerHTML = '<div class="w-3 h-3 bg-red-500 rounded-full mr-2"></div><span class="text-sm font-medium text-red-600 dark:text-red-400">Error</span>';
173
- });
174
- };
21
+ <!-- Admin Styles -->
22
+ <link rel="stylesheet" href="{% static 'admin/css/layout.css' %}">
23
+ <link rel="stylesheet" href="{% static 'admin/css/theme.css' %}">
24
+ <link rel="stylesheet" href="{% static 'admin/css/tabs.css' %}">
175
25
 
176
- function handleCommandData(data) {
177
- const output = document.getElementById('commandOutput');
178
- const status = document.getElementById('commandStatus');
179
-
180
- if (!output || !status) return;
181
-
182
- switch (data.type) {
183
- case 'start':
184
- // Clear previous content and add header
185
- output.innerHTML = '';
186
- addLogLine(output, `🚀 Starting command: ${data.command}`, 'info');
187
- addLogLine(output, `📝 Arguments: ${data.args.join(' ')}`, 'info');
188
- addLogLine(output, '', 'spacer');
189
- status.innerHTML = '<div class="w-3 h-3 bg-yellow-500 rounded-full mr-2 animate-pulse"></div><span class="text-sm font-medium text-gray-700 dark:text-gray-300">Executing...</span>';
190
- break;
191
- case 'output':
192
- addLogLine(output, data.line, 'output');
193
- scrollToBottom(output);
194
- break;
195
- case 'complete':
196
- const success = data.return_code === 0;
197
- status.innerHTML = success
198
- ? '<div class="w-3 h-3 bg-green-500 rounded-full mr-2"></div><span class="text-sm font-medium text-green-600 dark:text-green-400">Completed</span>'
199
- : '<div class="w-3 h-3 bg-red-500 rounded-full mr-2"></div><span class="text-sm font-medium text-red-600 dark:text-red-400">Failed</span>';
200
-
201
- addLogLine(output, '', 'spacer');
202
- let completionMessage = `${success ? '✅' : '❌'} Command completed with exit code: ${data.return_code}`;
203
- if (data.execution_time) {
204
- completionMessage += ` (${data.execution_time}s)`;
205
- }
206
- addLogLine(output, completionMessage, success ? 'success' : 'error');
207
- scrollToBottom(output);
208
- break;
209
- case 'error':
210
- addLogLine(output, `❌ Error: ${data.error}`, 'error');
211
- status.innerHTML = '<div class="w-3 h-3 bg-red-500 rounded-full mr-2"></div><span class="text-sm font-medium text-red-600 dark:text-red-400">Error</span>';
212
- scrollToBottom(output);
213
- break;
214
- }
215
- }
216
-
217
- function addLogLine(container, text, type = 'output') {
218
- const line = document.createElement('div');
219
- line.className = 'log-line';
220
-
221
- // Add type-specific styling
222
- switch (type) {
223
- case 'info':
224
- line.className += ' text-blue-600 dark:text-blue-400 font-medium';
225
- break;
226
- case 'success':
227
- line.className += ' text-green-600 dark:text-green-400 font-medium';
228
- break;
229
- case 'error':
230
- line.className += ' text-red-600 dark:text-red-400 font-medium';
231
- break;
232
- case 'spacer':
233
- line.className += ' h-2';
234
- break;
235
- default:
236
- line.className += ' text-gray-700 dark:text-gray-300';
237
- }
238
-
239
- // Set content
240
- if (type === 'spacer') {
241
- line.innerHTML = '&nbsp;';
242
- } else {
243
- line.textContent = text;
244
- }
245
-
246
- container.appendChild(line);
247
- }
248
-
249
- function scrollToBottom(element) {
250
- if (!element) return;
251
-
252
- // Принудительно скроллим элемент
253
- setTimeout(() => {
254
- element.scrollTop = element.scrollHeight;
255
- console.log('Scrolled to bottom:', element.scrollTop, element.scrollHeight);
256
- }, 50);
257
- }
258
-
259
- window.closeCommandModal = function() {
260
- const modal = document.getElementById('commandModal');
261
- if (modal) {
262
- modal.classList.add('hidden');
263
- }
264
- };
265
-
266
- function getCookie(name) {
267
- let cookieValue = null;
268
- if (document.cookie && document.cookie !== '') {
269
- const cookies = document.cookie.split(';');
270
- for (let i = 0; i < cookies.length; i++) {
271
- const cookie = cookies[i].trim();
272
- if (cookie.substring(0, name.length + 1) === (name + '=')) {
273
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
274
- break;
275
- }
276
- }
277
- }
278
- return cookieValue;
279
- }
280
- </script>
281
-
282
- <style>
283
- /* Dashboard tab styles */
284
- .tab-content {
285
- display: none;
286
- animation: fadeIn 0.3s ease-in-out;
287
- }
288
- .tab-content.active {
289
- display: block;
290
- }
291
- #dashboard-tabs button {
292
- cursor: pointer;
293
- transition: all 0.2s ease-in-out;
294
- }
295
-
296
- @keyframes fadeIn {
297
- from { opacity: 0; transform: translateY(10px); }
298
- to { opacity: 1; transform: translateY(0); }
299
- }
300
-
301
- /* 🎨 Theme classes - cross-theme compatible cards */
302
- .theme-card {
303
- background-color: rgba(255, 255, 255, 0.2) !important;
304
- backdrop-filter: blur(10px) !important;
305
- color: #111827 !important;
306
- border-color: rgba(229, 231, 235, 0.6) !important;
307
- border-radius: 10px !important;
308
- transition: all 0.2s ease;
309
- }
310
-
311
- html.dark .theme-card {
312
- background-color: rgba(31, 41, 55, 0.2) !important;
313
- backdrop-filter: blur(10px) !important;
314
- color: white !important;
315
- border-color: rgba(55, 65, 81, 0.6) !important;
316
- border-radius: 10px !important;
317
- }
318
-
319
- .theme-text {
320
- color: #111827 !important;
321
- }
322
-
323
- html.dark .theme-text {
324
- color: white !important;
325
- }
326
-
327
- .theme-border {
328
- border-color: rgba(229, 231, 235, 0.6) !important;
329
- }
330
-
331
- html.dark .theme-border {
332
- border-color: rgba(55, 65, 81, 0.6) !important;
333
- }
334
-
335
- /* Fix grid layout */
336
- .overview-grid {
337
- display: grid !important;
338
- grid-template-columns: 1fr !important;
339
- }
340
-
341
- @media (min-width: 1280px) {
342
- .overview-grid {
343
- grid-template-columns: 2fr 1fr !important;
344
- }
345
- }
346
-
347
- /* 🎯 Tab styling */
348
- #dashboard-tabs {
349
- border-bottom: 2px solid #e5e7eb !important;
350
- }
351
-
352
- html.dark #dashboard-tabs {
353
- border-bottom-color: #374151 !important;
354
- }
355
-
356
- #dashboard-tabs button {
357
- background-color: #f3f4f6 !important;
358
- color: #6b7280 !important;
359
- border-bottom: 2px solid transparent !important;
360
- transition: all 0.2s ease !important;
361
- }
362
-
363
- html.dark #dashboard-tabs button {
364
- background-color: #374151 !important;
365
- color: #9ca3af !important;
366
- }
367
-
368
- #dashboard-tabs button:hover {
369
- background-color: #e5e7eb !important;
370
- color: #374151 !important;
371
- }
372
-
373
- html.dark #dashboard-tabs button:hover {
374
- background-color: #4b5563 !important;
375
- color: #d1d5db !important;
376
- }
377
-
378
- /* Active tab */
379
- #dashboard-tabs button.active,
380
- #dashboard-tabs button[class*="bg-blue"] {
381
- background-color: #2563eb !important;
382
- color: white !important;
383
- border-bottom-color: #2563eb !important;
384
- }
385
-
386
- html.dark #dashboard-tabs button.active,
387
- html.dark #dashboard-tabs button[class*="bg-blue"] {
388
- background-color: #3b82f6 !important;
389
- color: white !important;
390
- border-bottom-color: #3b82f6 !important;
391
- }
392
-
393
- /* 🎯 Universal card borders - transparent cross-theme */
394
- .card-border,
395
- .border-base-200,
396
- [class*="border-base-200"],
397
- [class*="dark:border-base-700"] {
398
- border-color: rgba(229, 231, 235, 0.6) !important;
399
- }
400
-
401
- html.dark .card-border,
402
- html.dark .border-base-200,
403
- html.dark [class*="border-base-200"],
404
- html.dark [class*="dark:border-base-700"] {
405
- border-color: rgba(55, 65, 81, 0.6) !important;
406
- }
407
-
408
- /* 🎯 Icon spacing defaults - more specific targeting */
409
- .theme-card .material-icons:not(.no-margin) {
410
- margin-right: 0.75rem !important; /* 12px default spacing */
411
- }
412
-
413
- /* Override for specific contexts */
414
- .theme-card .flex.items-center .material-icons {
415
- margin-right: 0.75rem !important;
416
- }
417
-
418
- .theme-card .status-badge .material-icons {
419
- margin-right: 0.25rem !important; /* 4px for badges */
420
- }
421
-
422
- /* Fix for buttons and interactive elements */
423
- .theme-card button .material-icons,
424
- .theme-card a .material-icons {
425
- margin-right: 0.5rem !important;
426
- }
427
- </style>
26
+ <!-- JavaScript Component Notes:
27
+ - Dashboard tabs functionality: dashboard-tabs.js Alpine component
28
+ - Toggle category functionality: commands-panel.js Alpine component
29
+ - Command execution: command-modal.js Alpine component
30
+ - copyToClipboard and getCookie: dashboard.js (loaded in base_dashboard.html)
31
+ -->
428
32
  {% endblock %}
429
33
 
430
34
  {% block breadcrumbs %}{% endblock %}
@@ -443,120 +47,118 @@
443
47
  {% block content %}
444
48
  <!-- Main Dashboard Container -->
445
49
  {% component "unfold/components/container.html" %}
446
- <!-- Tabs Navigation -->
447
- <div class="flex gap-1 mb-4" id="dashboard-tabs">
448
- <button
449
- type="button"
450
- data-tab="0"
451
- class="px-4 py-3 rounded-t-lg focus:outline-none font-semibold active"
452
- >
453
- <span class="material-icons mr-2 align-middle text-sm">dashboard</span>
454
- Overview
455
- </button>
456
- <button
457
- type="button"
458
- data-tab="1"
459
- class="px-4 py-3 rounded-t-lg focus:outline-none"
460
- >
461
- <span class="material-icons mr-2 align-middle text-sm">public</span>
462
- API Zones
463
- </button>
464
- <button
465
- type="button"
466
- data-tab="2"
467
- class="px-4 py-3 rounded-t-lg focus:outline-none"
468
- >
469
- <span class="material-icons mr-2 align-middle text-sm">people</span>
470
- Users
471
- </button>
472
- <button
473
- type="button"
474
- data-tab="3"
475
- class="px-4 py-3 rounded-t-lg focus:outline-none"
476
- >
477
- <span class="material-icons mr-2 align-middle text-sm">settings</span>
478
- System
479
- </button>
480
- <button
481
- type="button"
482
- data-tab="4"
483
- class="px-4 py-3 rounded-t-lg focus:outline-none"
484
- >
485
- <span class="material-icons mr-2 align-middle text-sm">analytics</span>
486
- Statistics
487
- </button>
488
- <button
489
- type="button"
490
- data-tab="5"
491
- class="px-4 py-3 rounded-t-lg focus:outline-none"
492
- >
493
- <span class="material-icons mr-2 align-middle text-sm">apps</span>
494
- App Stats
495
- </button>
496
- <button
497
- type="button"
498
- data-tab="6"
499
- class="px-4 py-3 rounded-t-lg focus:outline-none"
500
- >
501
- <span class="material-icons mr-2 align-middle text-sm">terminal</span>
502
- Commands
503
- </button>
504
- <button
505
- type="button"
506
- data-tab="7"
507
- class="px-4 py-3 rounded-t-lg focus:outline-none"
508
- >
509
- <span class="material-icons mr-2 align-middle text-sm">description</span>
510
- Documentation
511
- </button>
512
- <button
513
- type="button"
514
- data-tab="8"
515
- class="px-4 py-3 rounded-t-lg focus:outline-none"
516
- >
517
- <span class="material-icons mr-2 align-middle text-sm">widgets</span>
518
- Widgets
519
- </button>
520
- </div>
50
+ <div x-data="dashboardTabs">
51
+ <!-- Tabs Navigation -->
52
+ <div class="flex gap-1 mb-4" id="dashboard-tabs">
53
+ <button
54
+ type="button"
55
+ @click="switchTab(0)"
56
+ :class="isActive(0) ? 'active' : ''"
57
+ class="px-4 py-3 rounded-t-lg focus:outline-none font-semibold"
58
+ >
59
+ <span class="material-icons mr-2 align-middle text-sm">dashboard</span>
60
+ Overview
61
+ </button>
62
+ <button
63
+ type="button"
64
+ @click="switchTab(1)"
65
+ :class="isActive(1) ? 'active' : ''"
66
+ class="px-4 py-3 rounded-t-lg focus:outline-none"
67
+ >
68
+ <span class="material-icons mr-2 align-middle text-sm">public</span>
69
+ API Zones
70
+ </button>
71
+ <button
72
+ type="button"
73
+ @click="switchTab(2)"
74
+ :class="isActive(2) ? 'active' : ''"
75
+ class="px-4 py-3 rounded-t-lg focus:outline-none"
76
+ >
77
+ <span class="material-icons mr-2 align-middle text-sm">people</span>
78
+ Users
79
+ </button>
80
+ <button
81
+ type="button"
82
+ @click="switchTab(3)"
83
+ :class="isActive(3) ? 'active' : ''"
84
+ class="px-4 py-3 rounded-t-lg focus:outline-none"
85
+ >
86
+ <span class="material-icons mr-2 align-middle text-sm">settings</span>
87
+ System
88
+ </button>
89
+ <button
90
+ type="button"
91
+ @click="switchTab(4)"
92
+ :class="isActive(4) ? 'active' : ''"
93
+ class="px-4 py-3 rounded-t-lg focus:outline-none"
94
+ >
95
+ <span class="material-icons mr-2 align-middle text-sm">analytics</span>
96
+ Statistics
97
+ </button>
98
+ <button
99
+ type="button"
100
+ @click="switchTab(5)"
101
+ :class="isActive(5) ? 'active' : ''"
102
+ class="px-4 py-3 rounded-t-lg focus:outline-none"
103
+ >
104
+ <span class="material-icons mr-2 align-middle text-sm">apps</span>
105
+ App Stats
106
+ </button>
107
+ <button
108
+ type="button"
109
+ @click="switchTab(6)"
110
+ :class="isActive(6) ? 'active' : ''"
111
+ class="px-4 py-3 rounded-t-lg focus:outline-none"
112
+ >
113
+ <span class="material-icons mr-2 align-middle text-sm">terminal</span>
114
+ Commands
115
+ </button>
116
+ <button
117
+ type="button"
118
+ @click="switchTab(7)"
119
+ :class="isActive(7) ? 'active' : ''"
120
+ class="px-4 py-3 rounded-t-lg focus:outline-none"
121
+ >
122
+ <span class="material-icons mr-2 align-middle text-sm">widgets</span>
123
+ Widgets
124
+ </button>
125
+ </div>
521
126
 
522
- <div class="flex flex-col gap-5">
127
+ <div class="flex flex-col gap-5">
523
128
 
524
129
  <!-- Tab Content with improved spacing -->
525
- <div class="tab-content active" data-tab="0">
130
+ <div class="tab-content" x-show="isActive(0)">
526
131
  {% block overview_tab %}{% endblock %}
527
132
  </div>
528
133
 
529
- <div class="tab-content" data-tab="1">
134
+ <div class="tab-content" x-show="isActive(1)">
530
135
  {% block zones_tab %}{% endblock %}
531
136
  </div>
532
137
 
533
- <div class="tab-content" data-tab="2">
138
+ <div class="tab-content" x-show="isActive(2)">
534
139
  {% block users_tab %}{% endblock %}
535
140
  </div>
536
141
 
537
- <div class="tab-content" data-tab="3">
142
+ <div class="tab-content" x-show="isActive(3)">
538
143
  {% block system_tab %}{% endblock %}
539
144
  </div>
540
145
 
541
- <div class="tab-content" data-tab="4">
146
+ <div class="tab-content" x-show="isActive(4)">
542
147
  {% block stats_tab %}{% endblock %}
543
148
  </div>
544
149
 
545
- <div class="tab-content" data-tab="5">
150
+ <div class="tab-content" x-show="isActive(5)">
546
151
  {% block app_stats_tab %}{% endblock %}
547
152
  </div>
548
153
 
549
- <div class="tab-content" data-tab="6">
154
+ <div class="tab-content" x-show="isActive(6)">
550
155
  {% block commands_tab %}{% endblock %}
551
156
  </div>
552
157
 
553
- <div class="tab-content" data-tab="7">
554
- {% block documentation_tab %}{% endblock %}
555
- </div>
556
-
557
- <div class="tab-content" data-tab="8">
158
+ <div class="tab-content" x-show="isActive(7)">
558
159
  {% block widgets_tab %}{% endblock %}
559
160
  </div>
161
+ </div>
560
162
  </div>
561
163
 
562
164
  <!-- Footer Section -->