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
@@ -1,171 +0,0 @@
1
- /**
2
- * Django CFG Commands JavaScript
3
- *
4
- * Handles command execution and modal interactions
5
- */
6
-
7
- class CommandExecutor {
8
- constructor() {
9
- this.modal = document.getElementById('commandModal');
10
- this.commandNameEl = document.getElementById('commandName');
11
- this.commandOutput = document.getElementById('commandOutput');
12
- this.commandStatus = document.getElementById('commandStatus');
13
- }
14
-
15
- execute(commandName) {
16
- if (!this.modal || !this.commandNameEl || !this.commandOutput || !this.commandStatus) {
17
- console.error('Command modal elements not found');
18
- return;
19
- }
20
-
21
- this.commandNameEl.textContent = commandName;
22
- this.commandOutput.textContent = '';
23
- this.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>';
24
- this.modal.classList.remove('hidden');
25
-
26
- fetch('/cfg/commands/execute/', {
27
- method: 'POST',
28
- headers: {
29
- 'Content-Type': 'application/json',
30
- 'X-CSRFToken': getCookie('csrftoken')
31
- },
32
- body: JSON.stringify({
33
- command: commandName,
34
- args: [],
35
- options: {}
36
- })
37
- })
38
- .then(response => {
39
- if (!response.ok) {
40
- throw new Error(`HTTP error! status: ${response.status}`);
41
- }
42
-
43
- const reader = response.body.getReader();
44
- const decoder = new TextDecoder();
45
-
46
- const readStream = () => {
47
- return reader.read().then(({done, value}) => {
48
- if (done) return;
49
-
50
- const chunk = decoder.decode(value);
51
- const lines = chunk.split('\n');
52
-
53
- lines.forEach(line => {
54
- if (line.startsWith('data: ')) {
55
- try {
56
- const data = JSON.parse(line.slice(6));
57
- this.handleCommandData(data);
58
- } catch (e) {
59
- console.error('Error parsing command data:', e);
60
- }
61
- }
62
- });
63
-
64
- return readStream();
65
- });
66
- };
67
-
68
- return readStream();
69
- })
70
- .catch(error => {
71
- console.error('Error executing command:', error);
72
- this.commandOutput.textContent += `\n❌ Error: ${error.message}`;
73
- this.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>';
74
- });
75
- }
76
-
77
- handleCommandData(data) {
78
- switch (data.type) {
79
- case 'start':
80
- this.commandOutput.innerHTML = '';
81
- this.addLogLine(`🚀 Starting command: ${data.command}`, 'info');
82
- this.addLogLine(`📝 Arguments: ${data.args.join(' ')}`, 'info');
83
- this.addLogLine('', 'spacer');
84
- this.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>';
85
- break;
86
- case 'output':
87
- this.addLogLine(data.line, 'output');
88
- this.scrollToBottom();
89
- break;
90
- case 'complete':
91
- const success = data.return_code === 0;
92
- this.commandStatus.innerHTML = success
93
- ? '<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>'
94
- : '<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>';
95
-
96
- this.addLogLine('', 'spacer');
97
- let completionMessage = `${success ? '✅' : '❌'} Command completed with exit code: ${data.return_code}`;
98
- if (data.execution_time) {
99
- completionMessage += ` (${data.execution_time}s)`;
100
- }
101
- this.addLogLine(completionMessage, success ? 'success' : 'error');
102
- this.scrollToBottom();
103
- break;
104
- case 'error':
105
- this.addLogLine(`❌ Error: ${data.error}`, 'error');
106
- this.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>';
107
- this.scrollToBottom();
108
- break;
109
- }
110
- }
111
-
112
- addLogLine(text, type = 'output') {
113
- const line = document.createElement('div');
114
- line.className = 'log-line';
115
-
116
- switch (type) {
117
- case 'info':
118
- line.className += ' text-blue-600 dark:text-blue-400 font-medium';
119
- break;
120
- case 'success':
121
- line.className += ' text-green-600 dark:text-green-400 font-medium';
122
- break;
123
- case 'error':
124
- line.className += ' text-red-600 dark:text-red-400 font-medium';
125
- break;
126
- case 'spacer':
127
- line.className += ' h-2';
128
- break;
129
- default:
130
- line.className += ' text-gray-700 dark:text-gray-300';
131
- }
132
-
133
- if (type === 'spacer') {
134
- line.innerHTML = '&nbsp;';
135
- } else {
136
- line.textContent = text;
137
- }
138
-
139
- this.commandOutput.appendChild(line);
140
- }
141
-
142
- scrollToBottom() {
143
- if (!this.commandOutput) return;
144
-
145
- setTimeout(() => {
146
- this.commandOutput.scrollTop = this.commandOutput.scrollHeight;
147
- }, 50);
148
- }
149
-
150
- close() {
151
- if (this.modal) {
152
- this.modal.classList.add('hidden');
153
- }
154
- }
155
- }
156
-
157
- // Global functions
158
- function executeCommand(commandName) {
159
- const executor = new CommandExecutor();
160
- executor.execute(commandName);
161
- }
162
-
163
- function closeCommandModal() {
164
- const executor = new CommandExecutor();
165
- executor.close();
166
- }
167
-
168
- // Export
169
- window.CommandExecutor = CommandExecutor;
170
- window.executeCommand = executeCommand;
171
- window.closeCommandModal = closeCommandModal;
@@ -1,126 +0,0 @@
1
- /**
2
- * Django CFG Dashboard JavaScript
3
- *
4
- * Handles dashboard tabs, navigation, and interactions
5
- */
6
-
7
- // Tab Management
8
- class DashboardTabs {
9
- constructor() {
10
- this.tabs = document.querySelectorAll('#dashboard-tabs button');
11
- this.contents = document.querySelectorAll('.tab-content');
12
- this.tabNames = ['overview', 'zones', 'users', 'system', 'stats', 'app-stats', 'commands'];
13
- this.init();
14
- }
15
-
16
- init() {
17
- if (!this.tabs.length || !this.contents.length) return;
18
-
19
- // Add click handlers
20
- this.tabs.forEach((tab, idx) => {
21
- tab.onclick = (e) => {
22
- e.preventDefault();
23
- this.switchTab(idx, true);
24
- };
25
- });
26
-
27
- // Handle browser back/forward
28
- window.addEventListener('hashchange', () => {
29
- const tabIndex = this.getTabFromHash();
30
- this.switchTab(tabIndex, false);
31
- });
32
-
33
- // Activate initial tab
34
- const initialTab = this.getTabFromHash();
35
- this.switchTab(initialTab, false);
36
- }
37
-
38
- switchTab(idx, updateHash = true) {
39
- this.tabs.forEach((t, i) => {
40
- if (i === idx) {
41
- t.classList.add('active');
42
- } else {
43
- t.classList.remove('active');
44
- }
45
- });
46
-
47
- this.contents.forEach((content, i) => {
48
- content.style.display = i === idx ? 'block' : 'none';
49
- content.classList.toggle('active', i === idx);
50
- });
51
-
52
- if (updateHash && this.tabNames[idx]) {
53
- history.replaceState(null, null, '#' + this.tabNames[idx]);
54
- }
55
- }
56
-
57
- getTabFromHash() {
58
- const hash = window.location.hash.substring(1);
59
- const tabIndex = this.tabNames.indexOf(hash);
60
- return tabIndex >= 0 ? tabIndex : 0;
61
- }
62
- }
63
-
64
- // Category Toggle
65
- function toggleCategory(category) {
66
- const content = document.getElementById(`content-${category}`);
67
- const icon = document.getElementById(`icon-${category}`);
68
-
69
- if (!content || !icon) return;
70
-
71
- if (content.style.display === 'none' || content.style.display === '') {
72
- content.style.display = 'block';
73
- icon.style.transform = 'rotate(0deg)';
74
- icon.textContent = 'expand_more';
75
- } else {
76
- content.style.display = 'none';
77
- icon.style.transform = 'rotate(-90deg)';
78
- icon.textContent = 'expand_less';
79
- }
80
- }
81
-
82
- // Clipboard Functions
83
- function copyToClipboard(text) {
84
- navigator.clipboard.writeText(text).then(() => {
85
- const button = event.target.closest('button');
86
- const originalText = button.innerHTML;
87
- button.innerHTML = '<span class="material-icons text-xs mr-1">check</span>Copied';
88
- button.classList.remove('bg-gray-100', 'dark:bg-gray-700', 'hover:bg-gray-200', 'dark:hover:bg-gray-600');
89
- button.classList.add('bg-green-600', 'hover:bg-green-700', 'dark:bg-green-500', 'dark:hover:bg-green-600', 'text-white');
90
-
91
- setTimeout(() => {
92
- button.innerHTML = originalText;
93
- button.classList.remove('bg-green-600', 'hover:bg-green-700', 'dark:bg-green-500', 'dark:hover:bg-green-600', 'text-white');
94
- button.classList.add('bg-gray-100', 'dark:bg-gray-700', 'hover:bg-gray-200', 'dark:hover:bg-gray-600');
95
- }, 2000);
96
- }).catch((err) => {
97
- console.error('Could not copy text: ', err);
98
- });
99
- }
100
-
101
- // Utility Functions
102
- function getCookie(name) {
103
- let cookieValue = null;
104
- if (document.cookie && document.cookie !== '') {
105
- const cookies = document.cookie.split(';');
106
- for (let i = 0; i < cookies.length; i++) {
107
- const cookie = cookies[i].trim();
108
- if (cookie.substring(0, name.length + 1) === (name + '=')) {
109
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
110
- break;
111
- }
112
- }
113
- }
114
- return cookieValue;
115
- }
116
-
117
- // Initialize on DOM load
118
- document.addEventListener('DOMContentLoaded', () => {
119
- new DashboardTabs();
120
- });
121
-
122
- // Export for global access
123
- window.DashboardTabs = DashboardTabs;
124
- window.toggleCategory = toggleCategory;
125
- window.copyToClipboard = copyToClipboard;
126
- window.getCookie = getCookie;
@@ -1,375 +0,0 @@
1
- /**
2
- * Management Commands JavaScript
3
- * Handles toggle, search, modal, and command execution functionality
4
- */
5
-
6
- // Global functions for category expansion
7
- window.toggleCategory = function(category) {
8
- const content = document.getElementById(`content-${category}`);
9
- const icon = document.getElementById(`icon-${category}`);
10
-
11
- if (!content || !icon) return;
12
-
13
- if (content.style.display === 'none' || content.style.display === '') {
14
- content.style.display = 'block';
15
- icon.style.transform = 'rotate(0deg)';
16
- icon.textContent = 'expand_more';
17
- } else {
18
- content.style.display = 'none';
19
- icon.style.transform = 'rotate(-90deg)';
20
- icon.textContent = 'expand_less';
21
- }
22
- };
23
-
24
- // Command execution functions
25
- window.copyToClipboard = function(text) {
26
- navigator.clipboard.writeText(text).then(function() {
27
- const button = event.target.closest('button');
28
- const originalText = button.innerHTML;
29
- button.innerHTML = '<span class="material-icons text-xs mr-1">check</span>Copied';
30
- button.classList.remove('bg-base-100', 'dark:bg-base-700', 'hover:bg-base-200', 'dark:hover:bg-base-600');
31
- button.classList.add('bg-green-600', 'hover:bg-green-700', 'dark:bg-green-500', 'dark:hover:bg-green-600', 'text-white');
32
-
33
- setTimeout(function() {
34
- button.innerHTML = originalText;
35
- button.classList.remove('bg-green-600', 'hover:bg-green-700', 'dark:bg-green-500', 'dark:hover:bg-green-600', 'text-white');
36
- button.classList.add('bg-base-100', 'dark:bg-base-700', 'hover:bg-base-200', 'dark:hover:bg-base-600');
37
- }, 2000);
38
- }).catch(function(err) {
39
- console.error('Could not copy text: ', err);
40
- });
41
- };
42
-
43
- window.executeCommand = function(commandName) {
44
- const modal = document.getElementById('commandModal');
45
- const commandNameEl = document.getElementById('commandName');
46
- const commandOutput = document.getElementById('commandOutput');
47
- const commandStatus = document.getElementById('commandStatus');
48
-
49
- if (!modal || !commandNameEl || !commandOutput || !commandStatus) {
50
- console.error('Command modal elements not found');
51
- return;
52
- }
53
-
54
- commandNameEl.textContent = commandName;
55
- commandOutput.textContent = '';
56
- 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-font-default-light dark:text-font-default-dark">Executing...</span>';
57
- modal.classList.remove('hidden');
58
-
59
- fetch('/cfg/commands/execute/', {
60
- method: 'POST',
61
- headers: {
62
- 'Content-Type': 'application/json',
63
- 'X-CSRFToken': getCookie('csrftoken')
64
- },
65
- body: JSON.stringify({
66
- command: commandName,
67
- args: [],
68
- options: {}
69
- })
70
- })
71
- .then(response => {
72
- if (!response.ok) {
73
- throw new Error(`HTTP error! status: ${response.status}`);
74
- }
75
-
76
- const reader = response.body.getReader();
77
- const decoder = new TextDecoder();
78
-
79
- function readStream() {
80
- return reader.read().then(({done, value}) => {
81
- if (done) return;
82
-
83
- const chunk = decoder.decode(value);
84
- const lines = chunk.split('\n');
85
-
86
- lines.forEach(line => {
87
- if (line.startsWith('data: ')) {
88
- try {
89
- const data = JSON.parse(line.slice(6));
90
- handleCommandData(data);
91
- } catch (e) {
92
- console.error('Error parsing command data:', e);
93
- }
94
- }
95
- });
96
-
97
- return readStream();
98
- });
99
- }
100
-
101
- return readStream();
102
- })
103
- .catch(error => {
104
- console.error('Error executing command:', error);
105
- commandOutput.textContent += `\n❌ Error: ${error.message}`;
106
- 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>';
107
- });
108
- };
109
-
110
- window.closeCommandModal = function() {
111
- const modal = document.getElementById('commandModal');
112
- if (modal) {
113
- modal.classList.add('hidden');
114
- }
115
- };
116
-
117
- // Helper functions
118
- function handleCommandData(data) {
119
- const output = document.getElementById('commandOutput');
120
- const status = document.getElementById('commandStatus');
121
-
122
- if (!output || !status) return;
123
-
124
- switch (data.type) {
125
- case 'start':
126
- output.innerHTML = '';
127
- addLogLine(output, `🚀 Starting command: ${data.command}`, 'info');
128
- addLogLine(output, `📝 Arguments: ${data.args.join(' ')}`, 'info');
129
- addLogLine(output, '', 'spacer');
130
- 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-font-default-light dark:text-font-default-dark">Executing...</span>';
131
- break;
132
- case 'output':
133
- addLogLine(output, data.line, 'output');
134
- scrollToBottom(output);
135
- break;
136
- case 'complete':
137
- const success = data.return_code === 0;
138
- status.innerHTML = success
139
- ? '<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>'
140
- : '<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>';
141
-
142
- addLogLine(output, '', 'spacer');
143
- let completionMessage = `${success ? '✅' : '❌'} Command completed with exit code: ${data.return_code}`;
144
- if (data.execution_time) {
145
- completionMessage += ` (${data.execution_time}s)`;
146
- }
147
- addLogLine(output, completionMessage, success ? 'success' : 'error');
148
- scrollToBottom(output);
149
- break;
150
- case 'error':
151
- addLogLine(output, `❌ ${data.message}`, 'error');
152
- 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>';
153
- scrollToBottom(output);
154
- break;
155
- }
156
- }
157
-
158
- function addLogLine(container, text, type = 'output') {
159
- const line = document.createElement('div');
160
- line.className = 'log-line';
161
-
162
- switch (type) {
163
- case 'info':
164
- line.className += ' text-blue-600 dark:text-blue-400';
165
- break;
166
- case 'success':
167
- line.className += ' text-green-600 dark:text-green-400 font-medium';
168
- break;
169
- case 'error':
170
- line.className += ' text-red-600 dark:text-red-400 font-medium';
171
- break;
172
- case 'spacer':
173
- line.style.height = '1em';
174
- break;
175
- default:
176
- line.className += ' text-font-default-light dark:text-font-default-dark';
177
- }
178
-
179
- line.textContent = text;
180
- container.appendChild(line);
181
- }
182
-
183
- function scrollToBottom(element) {
184
- element.scrollTop = element.scrollHeight;
185
- }
186
-
187
- function getCookie(name) {
188
- let cookieValue = null;
189
- if (document.cookie && document.cookie !== '') {
190
- const cookies = document.cookie.split(';');
191
- for (let i = 0; i < cookies.length; i++) {
192
- const cookie = cookies[i].trim();
193
- if (cookie.substring(0, name.length + 1) === (name + '=')) {
194
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
195
- break;
196
- }
197
- }
198
- }
199
- return cookieValue;
200
- }
201
-
202
- // Search functionality
203
- function searchCommands(query) {
204
- const searchQuery = query.toLowerCase().trim();
205
- const categories = document.querySelectorAll('[id^="content-"]');
206
- const clearButton = document.getElementById('clearSearch');
207
- const commandsCount = document.getElementById('commandsCount');
208
- let visibleCommands = 0;
209
-
210
- // Show/hide clear button
211
- if (searchQuery) {
212
- clearButton.classList.remove('hidden');
213
- } else {
214
- clearButton.classList.add('hidden');
215
- }
216
-
217
- categories.forEach(category => {
218
- const categoryName = category.id.replace('content-', '');
219
- const commands = category.querySelectorAll('.command-item');
220
- let categoryHasVisibleCommands = false;
221
-
222
- commands.forEach(command => {
223
- const commandName = command.querySelector('.command-name').textContent.toLowerCase();
224
- const commandDesc = command.querySelector('.command-description')?.textContent.toLowerCase() || '';
225
-
226
- if (!searchQuery || commandName.includes(searchQuery) || commandDesc.includes(searchQuery)) {
227
- command.style.display = 'block';
228
- categoryHasVisibleCommands = true;
229
- visibleCommands++;
230
- } else {
231
- command.style.display = 'none';
232
- }
233
- });
234
-
235
- // Show/hide category based on whether it has visible commands
236
- const categoryHeader = document.querySelector(`button[onclick="toggleCategory('${categoryName}')"]`);
237
- const categoryContainer = categoryHeader.parentElement;
238
-
239
- if (categoryHasVisibleCommands) {
240
- categoryContainer.style.display = 'block';
241
-
242
- // Auto-expand categories when searching
243
- if (searchQuery) {
244
- category.style.display = 'block';
245
- const icon = categoryHeader.querySelector('.material-icons');
246
- if (icon) {
247
- icon.textContent = 'expand_less';
248
- icon.style.transform = 'rotate(0deg)';
249
- }
250
- }
251
- } else {
252
- categoryContainer.style.display = 'none';
253
- }
254
- });
255
-
256
- // Update commands count
257
- if (commandsCount) {
258
- commandsCount.textContent = visibleCommands;
259
- }
260
-
261
- // Show "no results" message if no commands found
262
- showNoResultsMessage(visibleCommands === 0 && searchQuery);
263
- }
264
-
265
- function clearSearch() {
266
- const searchInput = document.getElementById('commandSearch');
267
- const clearButton = document.getElementById('clearSearch');
268
- const commandsCount = document.getElementById('commandsCount');
269
-
270
- searchInput.value = '';
271
- clearButton.classList.add('hidden');
272
-
273
- // Show all commands and categories
274
- const categories = document.querySelectorAll('[id^="content-"]');
275
- const allCommands = document.querySelectorAll('.command-item');
276
-
277
- categories.forEach(category => {
278
- const categoryName = category.id.replace('content-', '');
279
- const categoryHeader = document.querySelector(`button[onclick="toggleCategory('${categoryName}')"]`);
280
- categoryHeader.parentElement.style.display = 'block';
281
- // Reset to collapsed state
282
- category.style.display = 'none';
283
- const icon = categoryHeader.querySelector('.material-icons');
284
- if (icon) {
285
- icon.textContent = 'expand_less';
286
- icon.style.transform = 'rotate(-90deg)';
287
- }
288
- });
289
-
290
- allCommands.forEach(command => {
291
- command.style.display = 'block';
292
- });
293
-
294
- // Reset commands count to original total
295
- if (commandsCount && commandsCount.dataset.originalCount) {
296
- commandsCount.textContent = commandsCount.dataset.originalCount;
297
- }
298
-
299
- // Hide no results message
300
- showNoResultsMessage(false);
301
- }
302
-
303
- function showNoResultsMessage(show) {
304
- let noResultsDiv = document.getElementById('noSearchResults');
305
-
306
- if (show && !noResultsDiv) {
307
- // Create no results message
308
- noResultsDiv = document.createElement('div');
309
- noResultsDiv.id = 'noSearchResults';
310
- noResultsDiv.className = 'text-center py-12';
311
- noResultsDiv.innerHTML = `
312
- <div class="flex flex-col items-center">
313
- <span class="material-icons text-6xl text-base-400 dark:text-base-500 mb-4">search_off</span>
314
- <h3 class="text-lg font-medium text-font-important-light dark:text-font-important-dark mb-2">
315
- No Commands Found
316
- </h3>
317
- <p class="text-font-subtle-light dark:text-font-subtle-dark max-w-md mx-auto">
318
- No commands match your search criteria. Try different keywords or clear the search.
319
- </p>
320
- </div>
321
- `;
322
-
323
- // Insert after the commands container
324
- const commandsContainer = document.querySelector('.space-y-4');
325
- if (commandsContainer && commandsContainer.parentNode) {
326
- commandsContainer.parentNode.insertBefore(noResultsDiv, commandsContainer.nextSibling);
327
- }
328
- } else if (!show && noResultsDiv) {
329
- noResultsDiv.remove();
330
- }
331
- }
332
-
333
- // Initialize on page load
334
- document.addEventListener('DOMContentLoaded', function() {
335
- const searchInput = document.getElementById('commandSearch');
336
- const commandsCount = document.getElementById('commandsCount');
337
-
338
- // Store original count for reset
339
- if (commandsCount) {
340
- commandsCount.dataset.originalCount = commandsCount.textContent;
341
- }
342
-
343
- // Focus search with Ctrl+F or Cmd+F
344
- document.addEventListener('keydown', function(e) {
345
- if ((e.ctrlKey || e.metaKey) && e.key === 'f' && searchInput) {
346
- e.preventDefault();
347
- searchInput.focus();
348
- }
349
-
350
- // Clear search with Escape
351
- if (e.key === 'Escape' && document.activeElement === searchInput) {
352
- clearSearch();
353
- searchInput.blur();
354
- }
355
-
356
- // Close modal with Escape
357
- if (e.key === 'Escape') {
358
- closeCommandModal();
359
- }
360
- });
361
-
362
- // Close modal on background click
363
- const modal = document.getElementById('commandModal');
364
- if (modal) {
365
- modal.addEventListener('click', function(e) {
366
- if (e.target === modal) {
367
- closeCommandModal();
368
- }
369
- });
370
- }
371
- });
372
-
373
- // Export functions for global use
374
- window.searchCommands = searchCommands;
375
- window.clearSearch = clearSearch;