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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/agents/__init__.py +1 -1
- django_cfg/apps/agents/integration/registry.py +1 -1
- django_cfg/apps/agents/patterns/content_agents.py +1 -1
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +1 -1
- django_cfg/apps/centrifugo/views/dashboard.py +13 -0
- django_cfg/apps/centrifugo/views/testing_api.py +74 -15
- django_cfg/apps/tasks/views/dashboard.py +4 -4
- django_cfg/core/generation/integration_generators/api.py +5 -2
- django_cfg/management/commands/check_endpoints.py +1 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +2 -2
- django_cfg/modules/django_unfold/callbacks/main.py +27 -25
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/admin/css/constance.css +44 -0
- django_cfg/static/admin/css/dashboard.css +6 -170
- django_cfg/static/admin/css/layout.css +21 -0
- django_cfg/static/admin/css/tabs.css +95 -0
- django_cfg/static/admin/css/theme.css +74 -0
- django_cfg/static/admin/js/alpine/activity-tracker.js +55 -0
- django_cfg/static/admin/js/alpine/chart.js +101 -0
- django_cfg/static/admin/js/alpine/command-modal.js +159 -0
- django_cfg/static/admin/js/alpine/commands-panel.js +139 -0
- django_cfg/static/admin/js/alpine/commands-section.js +260 -0
- django_cfg/static/admin/js/alpine/dashboard-tabs.js +46 -0
- django_cfg/static/admin/js/alpine/system-metrics.js +20 -0
- django_cfg/static/admin/js/alpine/toggle-section.js +28 -0
- django_cfg/static/admin/js/utils.js +60 -0
- django_cfg/templates/admin/components/modal.html +1 -1
- django_cfg/templates/admin/constance/change_list.html +3 -42
- django_cfg/templates/admin/index.html +0 -8
- django_cfg/templates/admin/layouts/base_dashboard.html +4 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +104 -502
- django_cfg/templates/admin/sections/commands_section.html +374 -451
- django_cfg/templates/admin/sections/documentation_section.html +13 -33
- django_cfg/templates/admin/snippets/components/activity_tracker.html +27 -49
- django_cfg/templates/admin/snippets/components/charts_section.html +8 -74
- django_cfg/templates/admin/snippets/components/django_commands.html +94 -181
- django_cfg/templates/admin/snippets/components/system_metrics.html +18 -10
- django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +2 -2
- django_cfg/templates/admin/snippets/tabs/commands_tab.html +48 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +1 -190
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +1 -1
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/METADATA +1 -1
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/RECORD +47 -39
- django_cfg/static/admin/js/commands.js +0 -171
- django_cfg/static/admin/js/dashboard.js +0 -126
- django_cfg/templates/admin/components/management_commands.js +0 -375
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +0 -322
- django_cfg/templates/admin/snippets/components/recent_activity.html +0 -35
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.75.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 = ' ';
|
|
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;
|