django-cfg 1.4.75__py3-none-any.whl → 1.4.77__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_client/core/generator/python/models_generator.py +6 -6
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +0 -2
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +0 -1
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -1
- django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +7 -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.77.dist-info}/METADATA +1 -1
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.dist-info}/RECORD +52 -44
- 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.77.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.77.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
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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"
|
|
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
|
|
135
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
<
|
|
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"
|
|
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"
|
|
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
|
|
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
|
|
247
|
+
<span class="font-mono" x-text="currentCommand"></span>
|
|
175
248
|
</h3>
|
|
176
|
-
<button type="button"
|
|
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"
|
|
253
|
+
<div class="grow overflow-y-auto overflow-x-hidden w-full" x-ref="commandOutput">
|
|
181
254
|
<div class="p-4">
|
|
182
|
-
<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
|
|
188
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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 <command_name></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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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 = ' ';
|
|
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>
|