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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<link rel="stylesheet" href="{% static 'admin/css/prose-unfold.css' %}">
|
|
3
3
|
|
|
4
4
|
<!-- Documentation Section -->
|
|
5
|
-
<div class="space-y-6">
|
|
5
|
+
<div class="space-y-6" x-data="toggleSection()">
|
|
6
6
|
{% if error %}
|
|
7
7
|
<!-- Error State -->
|
|
8
8
|
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-default p-6">
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
{% for app_key, app in commands_by_module.items %}
|
|
18
18
|
<div class="app-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 mb-6">
|
|
19
19
|
<!-- App Header (Toggle) -->
|
|
20
|
-
<div
|
|
20
|
+
<div @click="toggleSection('{{ app_key }}')"
|
|
21
|
+
class="cursor-pointer select-none p-6 border-b border-base-200 dark:border-base-700 hover:bg-base-100/50 dark:hover:bg-base-900/30 transition-all duration-200"
|
|
21
22
|
data-app="{{ app_key }}">
|
|
22
23
|
<div class="w-full flex items-center justify-between">
|
|
23
24
|
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark flex items-center">
|
|
@@ -33,12 +34,18 @@
|
|
|
33
34
|
</div>
|
|
34
35
|
</div>
|
|
35
36
|
</h3>
|
|
36
|
-
<span
|
|
37
|
+
<span
|
|
38
|
+
class="material-symbols-outlined text-base-400 dark:text-base-500 transition-transform duration-200 select-none"
|
|
39
|
+
x-text="isSectionExpanded('{{ app_key }}') ? 'expand_less' : 'expand_more'"
|
|
40
|
+
></span>
|
|
37
41
|
</div>
|
|
38
42
|
</div>
|
|
39
43
|
|
|
40
44
|
<!-- App Content -->
|
|
41
|
-
<div class="app-content bg-base-50/30 dark:bg-base-900/30 p-6 space-y-4"
|
|
45
|
+
<div class="app-content bg-base-50/30 dark:bg-base-900/30 p-6 space-y-4"
|
|
46
|
+
data-app="{{ app_key }}"
|
|
47
|
+
x-show="isSectionExpanded('{{ app_key }}')"
|
|
48
|
+
x-cloak>
|
|
42
49
|
|
|
43
50
|
<!-- Commands List -->
|
|
44
51
|
<div class="space-y-3">
|
|
@@ -141,32 +148,5 @@
|
|
|
141
148
|
{% endif %}
|
|
142
149
|
</div>
|
|
143
150
|
|
|
144
|
-
<!--
|
|
145
|
-
<script>
|
|
146
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
147
|
-
// App toggle functionality
|
|
148
|
-
const appToggles = document.querySelectorAll('.app-toggle');
|
|
149
|
-
appToggles.forEach(toggle => {
|
|
150
|
-
toggle.addEventListener('click', function() {
|
|
151
|
-
const app = this.dataset.app;
|
|
152
|
-
const content = document.querySelector(`.app-content[data-app="${app}"]`);
|
|
153
|
-
const icon = this.querySelector('.toggle-icon');
|
|
154
|
-
|
|
155
|
-
if (content.style.display === 'none' || !content.style.display) {
|
|
156
|
-
content.style.display = 'block';
|
|
157
|
-
icon.textContent = 'expand_less';
|
|
158
|
-
} else {
|
|
159
|
-
content.style.display = 'none';
|
|
160
|
-
icon.textContent = 'expand_more';
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// Initialize - start collapsed
|
|
165
|
-
const app = toggle.dataset.app;
|
|
166
|
-
const content = document.querySelector(`.app-content[data-app="${app}"]`);
|
|
167
|
-
if (content) {
|
|
168
|
-
content.style.display = 'none';
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
</script>
|
|
151
|
+
<!-- Load Alpine component -->
|
|
152
|
+
<script src="{% static 'admin/js/alpine/toggle-section.js' %}"></script>
|
|
@@ -16,64 +16,42 @@
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
<!-- Activity Tracker Grid -->
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
{% if activity_tracker %}
|
|
20
|
+
<div class="overflow-x-auto pb-2" x-data='activityTracker({{ activity_tracker|safe }})'>
|
|
21
|
+
{% else %}
|
|
22
|
+
<div class="overflow-x-auto pb-2" x-data='activityTracker([])'>
|
|
23
|
+
{% endif %}
|
|
24
|
+
<template x-if="hasData">
|
|
21
25
|
<!-- GitHub-style heatmap visualization -->
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Create week columns
|
|
37
|
-
weeks.forEach(week => {
|
|
38
|
-
const weekColumn = document.createElement('div');
|
|
39
|
-
weekColumn.className = 'flex flex-col gap-1';
|
|
40
|
-
|
|
41
|
-
week.forEach(day => {
|
|
42
|
-
const cell = document.createElement('div');
|
|
43
|
-
cell.className = 'w-3 h-3 rounded-sm transition-all hover:ring-2 hover:ring-blue-400 cursor-pointer';
|
|
44
|
-
|
|
45
|
-
// Color based on activity level
|
|
46
|
-
if (day.count === 0) {
|
|
47
|
-
cell.className += ' bg-gray-200 dark:bg-gray-700';
|
|
48
|
-
} else if (day.count <= 2) {
|
|
49
|
-
cell.className += ' bg-green-200 dark:bg-green-800';
|
|
50
|
-
} else if (day.count <= 5) {
|
|
51
|
-
cell.className += ' bg-green-400 dark:bg-green-600';
|
|
52
|
-
} else if (day.count <= 10) {
|
|
53
|
-
cell.className += ' bg-green-600 dark:bg-green-500';
|
|
54
|
-
} else {
|
|
55
|
-
cell.className += ' bg-green-800 dark:bg-green-400';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
cell.title = `${day.date}: ${day.count} activities`;
|
|
59
|
-
weekColumn.appendChild(cell);
|
|
60
|
-
});
|
|
26
|
+
<div class="inline-flex gap-1">
|
|
27
|
+
<template x-for="(week, weekIndex) in weeks" :key="weekIndex">
|
|
28
|
+
<div class="flex flex-col gap-1">
|
|
29
|
+
<template x-for="(day, dayIndex) in week" :key="dayIndex">
|
|
30
|
+
<div
|
|
31
|
+
class="w-3 h-3 rounded-sm transition-all hover:ring-2 hover:ring-blue-400 cursor-pointer"
|
|
32
|
+
:class="getCellColor(day.count)"
|
|
33
|
+
:title="getCellTitle(day)"
|
|
34
|
+
></div>
|
|
35
|
+
</template>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
61
40
|
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
})();
|
|
66
|
-
</script>
|
|
67
|
-
{% else %}
|
|
41
|
+
<template x-if="!hasData">
|
|
68
42
|
<div class="flex items-center justify-center p-8 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
|
69
43
|
<div class="text-center">
|
|
70
44
|
<span class="material-icons text-4xl text-gray-400 dark:text-gray-500 mb-2">timeline</span>
|
|
71
45
|
<p class="text-gray-600 dark:text-gray-400">No activity data available</p>
|
|
72
46
|
</div>
|
|
73
47
|
</div>
|
|
74
|
-
|
|
48
|
+
</template>
|
|
75
49
|
</div>
|
|
76
50
|
|
|
51
|
+
<!-- Load Alpine component -->
|
|
52
|
+
{% load static %}
|
|
53
|
+
<script src="{% static 'admin/js/alpine/activity-tracker.js' %}"></script>
|
|
54
|
+
|
|
77
55
|
<!-- Summary Stats -->
|
|
78
56
|
<div class="grid grid-cols-3 gap-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
79
57
|
<div class="text-center">
|
|
@@ -85,7 +63,7 @@
|
|
|
85
63
|
<div class="text-xs text-gray-600 dark:text-gray-400">Weeks</div>
|
|
86
64
|
</div>
|
|
87
65
|
<div class="text-center">
|
|
88
|
-
<div class="text-lg font-semibold text-blue-600 dark:text-blue-400">{{ activity_tracker|length|default:
|
|
66
|
+
<div class="text-lg font-semibold text-blue-600 dark:text-blue-400">{{ activity_tracker|length|default:0 }}</div>
|
|
89
67
|
<div class="text-xs text-gray-600 dark:text-gray-400">Data points</div>
|
|
90
68
|
</div>
|
|
91
69
|
</div>
|
|
@@ -32,44 +32,9 @@
|
|
|
32
32
|
<span class="text-sm text-font-subtle-light dark:text-font-subtle-dark">Growth Trends</span>
|
|
33
33
|
</div>
|
|
34
34
|
{% if charts.user_registrations %}
|
|
35
|
-
<div class="relative h-[300px]">
|
|
36
|
-
<canvas
|
|
35
|
+
<div class="relative h-[300px]" x-data='chart({{ charts.user_registrations_json|safe }}, "line")'>
|
|
36
|
+
<canvas x-ref="canvas"></canvas>
|
|
37
37
|
</div>
|
|
38
|
-
<script>
|
|
39
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
40
|
-
const ctx = document.getElementById('userRegistrationsChart');
|
|
41
|
-
const chartData = {{ charts.user_registrations_json|safe }};
|
|
42
|
-
|
|
43
|
-
if (ctx && typeof Chart !== 'undefined') {
|
|
44
|
-
try {
|
|
45
|
-
new Chart(ctx, {
|
|
46
|
-
type: 'line',
|
|
47
|
-
data: chartData,
|
|
48
|
-
options: {
|
|
49
|
-
responsive: true,
|
|
50
|
-
maintainAspectRatio: false,
|
|
51
|
-
plugins: {
|
|
52
|
-
legend: {
|
|
53
|
-
display: true,
|
|
54
|
-
position: 'top'
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
scales: {
|
|
58
|
-
y: {
|
|
59
|
-
beginAtZero: true,
|
|
60
|
-
ticks: {
|
|
61
|
-
precision: 0
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error('Error creating registration chart:', error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
</script>
|
|
73
38
|
|
|
74
39
|
<!-- Fallback data table if chart doesn't render -->
|
|
75
40
|
<div class="mt-4 text-xs text-font-subtle-light dark:text-font-subtle-dark">
|
|
@@ -106,44 +71,9 @@
|
|
|
106
71
|
<span class="text-sm text-font-subtle-light dark:text-font-subtle-dark">Activity Levels</span>
|
|
107
72
|
</div>
|
|
108
73
|
{% if charts.user_activity %}
|
|
109
|
-
<div class="relative h-[300px]">
|
|
110
|
-
<canvas
|
|
74
|
+
<div class="relative h-[300px]" x-data='chart({{ charts.user_activity_json|safe }}, "bar")'>
|
|
75
|
+
<canvas x-ref="canvas"></canvas>
|
|
111
76
|
</div>
|
|
112
|
-
<script>
|
|
113
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
114
|
-
const ctx = document.getElementById('userActivityChart');
|
|
115
|
-
const chartData = {{ charts.user_activity_json|safe }};
|
|
116
|
-
|
|
117
|
-
if (ctx && typeof Chart !== 'undefined') {
|
|
118
|
-
try {
|
|
119
|
-
new Chart(ctx, {
|
|
120
|
-
type: 'bar',
|
|
121
|
-
data: chartData,
|
|
122
|
-
options: {
|
|
123
|
-
responsive: true,
|
|
124
|
-
maintainAspectRatio: false,
|
|
125
|
-
plugins: {
|
|
126
|
-
legend: {
|
|
127
|
-
display: true,
|
|
128
|
-
position: 'top'
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
scales: {
|
|
132
|
-
y: {
|
|
133
|
-
beginAtZero: true,
|
|
134
|
-
ticks: {
|
|
135
|
-
precision: 0
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error('Error creating activity chart:', error);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
</script>
|
|
147
77
|
|
|
148
78
|
<!-- Fallback data table if chart doesn't render -->
|
|
149
79
|
<div class="mt-4 text-xs text-font-subtle-light dark:text-font-subtle-dark">
|
|
@@ -177,3 +107,7 @@
|
|
|
177
107
|
</div>
|
|
178
108
|
{% endif %}
|
|
179
109
|
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Load Chart Alpine component -->
|
|
112
|
+
{% load static %}
|
|
113
|
+
<script src="{% static 'admin/js/alpine/chart.js' %}"></script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{% load unfold %}
|
|
2
2
|
|
|
3
3
|
<!-- Django Commands Section -->
|
|
4
|
-
<div class="mt-8 w-full">
|
|
4
|
+
<div class="mt-8 w-full" x-data="commandsPanel({{ django_commands.total_commands }})">
|
|
5
5
|
<!-- Header -->
|
|
6
6
|
<div class="flex items-center justify-between mb-6">
|
|
7
7
|
<div class="flex items-center">
|
|
@@ -12,26 +12,27 @@
|
|
|
12
12
|
Django Commands
|
|
13
13
|
</h2>
|
|
14
14
|
<span class="ml-4 text-xs text-font-subtle-light dark:text-font-subtle-dark bg-base-100 dark:bg-base-800 px-2 py-1 rounded-full">
|
|
15
|
-
<span
|
|
15
|
+
<span x-text="visibleCommands"></span> commands
|
|
16
16
|
</span>
|
|
17
17
|
</div>
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
<!-- Search Box -->
|
|
20
20
|
<div class="flex items-center space-x-3">
|
|
21
21
|
<div class="bg-white border border-base-200 flex flex-row items-center px-3 rounded-default relative shadow-xs w-64 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-primary-600 dark:bg-base-900 dark:border-base-700">
|
|
22
22
|
<span class="material-symbols-outlined md-18 text-base-400 dark:text-base-500">search</span>
|
|
23
|
-
<input
|
|
24
|
-
type="text"
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
<input
|
|
24
|
+
type="text"
|
|
25
|
+
x-ref="searchInput"
|
|
26
|
+
x-model="searchQuery"
|
|
27
|
+
@input="search()"
|
|
28
|
+
placeholder="Search commands..."
|
|
27
29
|
class="grow font-medium min-w-0 overflow-hidden p-2 placeholder-font-subtle-light truncate focus:outline-hidden dark:bg-base-900 dark:placeholder-font-subtle-dark dark:text-font-default-dark"
|
|
28
|
-
oninput="searchCommands(this.value)"
|
|
29
30
|
>
|
|
30
31
|
</div>
|
|
31
|
-
<button
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class="px-3 py-2 text-xs text-font-subtle-light dark:text-font-subtle-dark hover:text-font-default-light dark:hover:text-font-default-dark transition-colors
|
|
32
|
+
<button
|
|
33
|
+
x-show="showClearButton"
|
|
34
|
+
@click="clearSearch()"
|
|
35
|
+
class="px-3 py-2 text-xs text-font-subtle-light dark:text-font-subtle-dark hover:text-font-default-light dark:hover:text-font-default-dark transition-colors"
|
|
35
36
|
>
|
|
36
37
|
Clear
|
|
37
38
|
</button>
|
|
@@ -44,8 +45,10 @@
|
|
|
44
45
|
{% for category, commands in django_commands.categorized.items %}
|
|
45
46
|
<div class="bg-white dark:bg-base-900 rounded-lg border border-base-200 dark:border-base-700 overflow-hidden shadow-sm mb-4">
|
|
46
47
|
<!-- Category Header -->
|
|
47
|
-
<button
|
|
48
|
-
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
data-category="{{ category }}"
|
|
51
|
+
@click="toggleCategory('{{ category }}')"
|
|
49
52
|
class="w-full p-4 flex items-center justify-between hover:bg-base-100 dark:hover:bg-base-700 transition-colors"
|
|
50
53
|
>
|
|
51
54
|
<div class="flex items-center">
|
|
@@ -57,13 +60,20 @@
|
|
|
57
60
|
{{ commands|length }} commands
|
|
58
61
|
</span>
|
|
59
62
|
</div>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
<span
|
|
64
|
+
class="material-icons text-font-subtle-light dark:text-font-subtle-dark transition-transform"
|
|
65
|
+
:class="isCategoryExpanded('{{ category }}') ? 'rotate-0' : '-rotate-90'"
|
|
66
|
+
x-text="isCategoryExpanded('{{ category }}') ? 'expand_less' : 'expand_more'"
|
|
67
|
+
></span>
|
|
63
68
|
</button>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
|
|
70
|
+
<!-- Collapsible Content -->
|
|
71
|
+
<div
|
|
72
|
+
id="content-{{ category }}"
|
|
73
|
+
class="border-t border-base-200 dark:border-base-700"
|
|
74
|
+
x-show="isCategoryExpanded('{{ category }}')"
|
|
75
|
+
x-cloak
|
|
76
|
+
>
|
|
67
77
|
<div class="p-4">
|
|
68
78
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
69
79
|
{% for command in commands %}
|
|
@@ -122,17 +132,17 @@
|
|
|
122
132
|
{% endif %}
|
|
123
133
|
</div>
|
|
124
134
|
|
|
125
|
-
<!-- Action Buttons
|
|
135
|
+
<!-- Action Buttons -->
|
|
126
136
|
<div class="flex gap-2">
|
|
127
137
|
<button
|
|
128
|
-
|
|
138
|
+
@click="window.copyToClipboard('python manage.py {{ command.name }}')"
|
|
129
139
|
class="flex-1 inline-flex items-center justify-center px-3 py-2 bg-base-100 dark:bg-base-700 hover:bg-base-200 dark:hover:bg-base-600 text-font-default-light dark:text-font-default-dark rounded-lg text-xs font-medium transition-colors"
|
|
130
140
|
title="Copy command to clipboard">
|
|
131
141
|
<span class="material-icons text-xs mr-1">content_copy</span>
|
|
132
142
|
Copy
|
|
133
143
|
</button>
|
|
134
144
|
<button
|
|
135
|
-
|
|
145
|
+
@click="window.executeCommand('{{ command.name }}')"
|
|
136
146
|
class="flex-1 inline-flex items-center justify-center px-3 py-2 bg-green-600 hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600 text-white rounded-lg text-xs font-medium transition-colors"
|
|
137
147
|
title="Execute command">
|
|
138
148
|
<span class="material-icons text-xs mr-1">play_arrow</span>
|
|
@@ -148,7 +158,20 @@
|
|
|
148
158
|
</div>
|
|
149
159
|
{% endfor %}
|
|
150
160
|
</div>
|
|
151
|
-
|
|
161
|
+
|
|
162
|
+
<!-- No Results Message -->
|
|
163
|
+
<div x-show="showNoResults" class="text-center py-12">
|
|
164
|
+
<div class="flex flex-col items-center">
|
|
165
|
+
<span class="material-icons text-6xl text-base-400 dark:text-base-500 mb-4">search_off</span>
|
|
166
|
+
<h3 class="text-lg font-medium text-font-important-light dark:text-font-important-dark mb-2">
|
|
167
|
+
No Commands Found
|
|
168
|
+
</h3>
|
|
169
|
+
<p class="text-font-subtle-light dark:text-font-subtle-dark max-w-md mx-auto">
|
|
170
|
+
No commands match your search criteria. Try different keywords or clear the search.
|
|
171
|
+
</p>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
152
175
|
{% else %}
|
|
153
176
|
<!-- No Commands -->
|
|
154
177
|
<div class="text-center py-12">
|
|
@@ -165,8 +188,8 @@
|
|
|
165
188
|
{% endif %}
|
|
166
189
|
</div>
|
|
167
190
|
|
|
168
|
-
<!-- Command Execution Modal -->
|
|
169
|
-
<div
|
|
191
|
+
<!-- Command Execution Modal (Alpine.js) -->
|
|
192
|
+
<div x-data="commandModal" x-show="open" x-cloak class="fixed inset-0 bg-black/80 backdrop-blur-sm z-50">
|
|
170
193
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
171
194
|
<div class="bg-white dark:bg-base-900 rounded-lg shadow-2xl max-w-4xl w-full max-h-[80vh] flex flex-col border border-base-200 dark:border-base-700">
|
|
172
195
|
<!-- Modal Header -->
|
|
@@ -180,29 +203,59 @@
|
|
|
180
203
|
Command Execution
|
|
181
204
|
</h3>
|
|
182
205
|
<p class="text-sm text-font-subtle-light dark:text-font-subtle-dark">
|
|
183
|
-
Running: <span
|
|
206
|
+
Running: <span x-text="commandName" class="font-mono text-primary-600 dark:text-primary-400"></span>
|
|
184
207
|
</p>
|
|
185
208
|
</div>
|
|
186
209
|
</div>
|
|
187
|
-
<button
|
|
210
|
+
<button @click="close()" class="p-2 text-font-subtle-light dark:text-font-subtle-dark hover:text-font-default-light dark:hover:text-font-default-dark hover:bg-base-100 dark:hover:bg-base-700 rounded-lg transition-colors">
|
|
188
211
|
<span class="material-icons">close</span>
|
|
189
212
|
</button>
|
|
190
213
|
</div>
|
|
191
|
-
|
|
214
|
+
|
|
215
|
+
<!-- Tabs Navigation -->
|
|
216
|
+
<div class="flex gap-1 px-4 pt-2 border-b border-base-200 dark:border-base-700">
|
|
217
|
+
<button
|
|
218
|
+
@click="activeTab = 'output'"
|
|
219
|
+
:class="activeTab === 'output' ? 'active' : ''"
|
|
220
|
+
class="command-tab px-4 py-2 text-sm font-medium rounded-t-lg transition-colors"
|
|
221
|
+
>
|
|
222
|
+
<span class="material-icons text-sm mr-1 align-middle">terminal</span>
|
|
223
|
+
Output
|
|
224
|
+
</button>
|
|
225
|
+
<button
|
|
226
|
+
@click="activeTab = 'docs'"
|
|
227
|
+
:class="activeTab === 'docs' ? 'active' : ''"
|
|
228
|
+
class="command-tab px-4 py-2 text-sm font-medium rounded-t-lg transition-colors"
|
|
229
|
+
>
|
|
230
|
+
<span class="material-icons text-sm mr-1 align-middle">help_outline</span>
|
|
231
|
+
Documentation
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
192
235
|
<!-- Modal Body -->
|
|
193
|
-
<div class="flex-1 p-4
|
|
194
|
-
|
|
236
|
+
<div class="flex-1 p-4 min-h-0">
|
|
237
|
+
<!-- Output Tab Content -->
|
|
238
|
+
<div x-show="activeTab === 'output'" class="h-full">
|
|
239
|
+
<div x-html="outputHtml" class="bg-base-100 dark:bg-base-800 rounded-lg border border-base-200 dark:border-base-700 overflow-y-auto p-4 text-sm font-mono text-font-default-light dark:text-font-default-dark whitespace-pre-wrap leading-relaxed break-words h-full"></div>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<!-- Documentation Tab Content -->
|
|
243
|
+
<div x-show="activeTab === 'docs'" class="h-full">
|
|
244
|
+
<div class="bg-base-100 dark:bg-base-800 rounded-lg border border-base-200 dark:border-base-700 overflow-y-auto p-4 h-full">
|
|
245
|
+
<div x-html="docsHtml" class="prose prose-sm dark:prose-invert max-w-none"></div>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
195
248
|
</div>
|
|
196
|
-
|
|
249
|
+
|
|
197
250
|
<!-- Modal Footer -->
|
|
198
251
|
<div class="flex items-center justify-between p-4 border-t border-base-200 dark:border-base-700 bg-base-50 dark:bg-base-800">
|
|
199
252
|
<div class="flex items-center space-x-3">
|
|
200
|
-
<div
|
|
201
|
-
<div class="w-3 h-3
|
|
202
|
-
<span class="text-sm font-medium text-font-default-light dark:text-font-default-dark"
|
|
253
|
+
<div class="flex items-center">
|
|
254
|
+
<div :class="statusClass" class="w-3 h-3 rounded-full mr-2"></div>
|
|
255
|
+
<span x-text="statusText" class="text-sm font-medium text-font-default-light dark:text-font-default-dark"></span>
|
|
203
256
|
</div>
|
|
204
257
|
</div>
|
|
205
|
-
<button
|
|
258
|
+
<button @click="close()" class="px-4 py-2 bg-base-100 dark:bg-base-700 hover:bg-base-200 dark:hover:bg-base-600 text-font-default-light dark:text-font-default-dark rounded-lg transition-colors font-medium">
|
|
206
259
|
Close
|
|
207
260
|
</button>
|
|
208
261
|
</div>
|
|
@@ -210,148 +263,8 @@
|
|
|
210
263
|
</div>
|
|
211
264
|
</div>
|
|
212
265
|
|
|
213
|
-
<!--
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const clearButton = document.getElementById('clearSearch');
|
|
219
|
-
const commandsCount = document.getElementById('commandsCount');
|
|
220
|
-
let visibleCommands = 0;
|
|
221
|
-
|
|
222
|
-
// Show/hide clear button
|
|
223
|
-
if (searchQuery) {
|
|
224
|
-
clearButton.classList.remove('hidden');
|
|
225
|
-
} else {
|
|
226
|
-
clearButton.classList.add('hidden');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
categories.forEach(category => {
|
|
230
|
-
const categoryName = category.id.replace('content-', '');
|
|
231
|
-
const commands = category.querySelectorAll('.command-item');
|
|
232
|
-
let categoryHasVisibleCommands = false;
|
|
233
|
-
|
|
234
|
-
commands.forEach(command => {
|
|
235
|
-
const commandName = command.querySelector('.command-name').textContent.toLowerCase();
|
|
236
|
-
const commandDesc = command.querySelector('.command-description')?.textContent.toLowerCase() || '';
|
|
237
|
-
|
|
238
|
-
if (!searchQuery || commandName.includes(searchQuery) || commandDesc.includes(searchQuery)) {
|
|
239
|
-
command.style.display = 'block';
|
|
240
|
-
categoryHasVisibleCommands = true;
|
|
241
|
-
visibleCommands++;
|
|
242
|
-
} else {
|
|
243
|
-
command.style.display = 'none';
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// Show/hide category based on whether it has visible commands
|
|
248
|
-
const categoryHeader = document.querySelector(`button[onclick="toggleCategory('${categoryName}')"]`);
|
|
249
|
-
const categoryContainer = categoryHeader.parentElement;
|
|
250
|
-
|
|
251
|
-
if (categoryHasVisibleCommands) {
|
|
252
|
-
categoryContainer.style.display = 'block';
|
|
253
|
-
|
|
254
|
-
// Auto-expand categories when searching
|
|
255
|
-
if (searchQuery) {
|
|
256
|
-
category.style.display = 'block';
|
|
257
|
-
const icon = categoryHeader.querySelector('.material-icons');
|
|
258
|
-
if (icon) {
|
|
259
|
-
icon.textContent = 'expand_less';
|
|
260
|
-
icon.style.transform = 'rotate(0deg)';
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
} else {
|
|
264
|
-
categoryContainer.style.display = 'none';
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Update commands count
|
|
269
|
-
commandsCount.textContent = visibleCommands;
|
|
270
|
-
|
|
271
|
-
// Show "no results" message if no commands found
|
|
272
|
-
showNoResultsMessage(visibleCommands === 0 && searchQuery);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function clearSearch() {
|
|
276
|
-
const searchInput = document.getElementById('commandSearch');
|
|
277
|
-
const clearButton = document.getElementById('clearSearch');
|
|
278
|
-
const commandsCount = document.getElementById('commandsCount');
|
|
279
|
-
|
|
280
|
-
searchInput.value = '';
|
|
281
|
-
clearButton.classList.add('hidden');
|
|
282
|
-
|
|
283
|
-
// Show all commands and categories
|
|
284
|
-
const categories = document.querySelectorAll('[id^="content-"]');
|
|
285
|
-
const allCommands = document.querySelectorAll('.command-item');
|
|
286
|
-
|
|
287
|
-
categories.forEach(category => {
|
|
288
|
-
const categoryName = category.id.replace('content-', '');
|
|
289
|
-
const categoryHeader = document.querySelector(`button[onclick="toggleCategory('${categoryName}')"]`);
|
|
290
|
-
categoryHeader.parentElement.style.display = 'block';
|
|
291
|
-
// Reset to collapsed state
|
|
292
|
-
category.style.display = 'none';
|
|
293
|
-
const icon = categoryHeader.querySelector('.material-icons');
|
|
294
|
-
if (icon) {
|
|
295
|
-
icon.textContent = 'expand_less';
|
|
296
|
-
icon.style.transform = 'rotate(-90deg)';
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
allCommands.forEach(command => {
|
|
301
|
-
command.style.display = 'block';
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// Reset commands count
|
|
305
|
-
commandsCount.textContent = '{{ django_commands.total_commands }}';
|
|
306
|
-
|
|
307
|
-
// Hide no results message
|
|
308
|
-
showNoResultsMessage(false);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function showNoResultsMessage(show) {
|
|
312
|
-
let noResultsDiv = document.getElementById('noSearchResults');
|
|
313
|
-
|
|
314
|
-
if (show && !noResultsDiv) {
|
|
315
|
-
// Create no results message
|
|
316
|
-
noResultsDiv = document.createElement('div');
|
|
317
|
-
noResultsDiv.id = 'noSearchResults';
|
|
318
|
-
noResultsDiv.className = 'text-center py-12';
|
|
319
|
-
noResultsDiv.innerHTML = `
|
|
320
|
-
<div class="flex flex-col items-center">
|
|
321
|
-
<span class="material-icons text-6xl text-base-400 dark:text-base-500 mb-4">search_off</span>
|
|
322
|
-
<h3 class="text-lg font-medium text-font-important-light dark:text-font-important-dark mb-2">
|
|
323
|
-
No Commands Found
|
|
324
|
-
</h3>
|
|
325
|
-
<p class="text-font-subtle-light dark:text-font-subtle-dark max-w-md mx-auto">
|
|
326
|
-
No commands match your search criteria. Try different keywords or clear the search.
|
|
327
|
-
</p>
|
|
328
|
-
</div>
|
|
329
|
-
`;
|
|
330
|
-
|
|
331
|
-
// Insert after the commands container
|
|
332
|
-
const commandsContainer = document.querySelector('.space-y-4');
|
|
333
|
-
commandsContainer.parentNode.insertBefore(noResultsDiv, commandsContainer.nextSibling);
|
|
334
|
-
} else if (!show && noResultsDiv) {
|
|
335
|
-
noResultsDiv.remove();
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Add search keyboard shortcuts
|
|
340
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
341
|
-
const searchInput = document.getElementById('commandSearch');
|
|
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') {
|
|
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
|
-
});
|
|
357
|
-
</script>
|
|
266
|
+
<!-- Alpine.js Components & Styles -->
|
|
267
|
+
{% load static %}
|
|
268
|
+
<link rel="stylesheet" href="{% static 'admin/css/tabs.css' %}">
|
|
269
|
+
<script src="{% static 'admin/js/alpine/command-modal.js' %}"></script>
|
|
270
|
+
<script src="{% static 'admin/js/alpine/commands-panel.js' %}"></script>
|