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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Tabs Styles
|
|
3
|
+
* Styling for tab navigation and tab content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Tab Content Animation */
|
|
7
|
+
.tab-content {
|
|
8
|
+
animation: fadeIn 0.3s ease-in-out;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@keyframes fadeIn {
|
|
12
|
+
from {
|
|
13
|
+
opacity: 0;
|
|
14
|
+
transform: translateY(10px);
|
|
15
|
+
}
|
|
16
|
+
to {
|
|
17
|
+
opacity: 1;
|
|
18
|
+
transform: translateY(0);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Dashboard Tabs Navigation */
|
|
23
|
+
#dashboard-tabs {
|
|
24
|
+
border-bottom: 2px solid #e5e7eb !important;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
html.dark #dashboard-tabs {
|
|
28
|
+
border-bottom-color: #374151 !important;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#dashboard-tabs button {
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transition: all 0.2s ease-in-out;
|
|
34
|
+
background-color: #f3f4f6 !important;
|
|
35
|
+
color: #6b7280 !important;
|
|
36
|
+
border-bottom: 2px solid transparent !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
html.dark #dashboard-tabs button {
|
|
40
|
+
background-color: #374151 !important;
|
|
41
|
+
color: #9ca3af !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#dashboard-tabs button:hover {
|
|
45
|
+
background-color: #e5e7eb !important;
|
|
46
|
+
color: #374151 !important;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
html.dark #dashboard-tabs button:hover {
|
|
50
|
+
background-color: #4b5563 !important;
|
|
51
|
+
color: #d1d5db !important;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Active Tab */
|
|
55
|
+
#dashboard-tabs button.active,
|
|
56
|
+
#dashboard-tabs button[class*="bg-blue"] {
|
|
57
|
+
background-color: #2563eb !important;
|
|
58
|
+
color: white !important;
|
|
59
|
+
border-bottom-color: #2563eb !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
html.dark #dashboard-tabs button.active,
|
|
63
|
+
html.dark #dashboard-tabs button[class*="bg-blue"] {
|
|
64
|
+
background-color: #3b82f6 !important;
|
|
65
|
+
color: white !important;
|
|
66
|
+
border-bottom-color: #3b82f6 !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Command Modal Tabs */
|
|
70
|
+
.command-tab {
|
|
71
|
+
background-color: transparent;
|
|
72
|
+
color: #6b7280;
|
|
73
|
+
transition: all 0.2s ease;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.command-tab:hover {
|
|
77
|
+
background-color: #f3f4f6;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
html.dark .command-tab {
|
|
81
|
+
color: #9ca3af;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
html.dark .command-tab:hover {
|
|
85
|
+
background-color: #374151;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.command-tab.active {
|
|
89
|
+
background-color: #3b82f6;
|
|
90
|
+
color: white;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
html.dark .command-tab.active {
|
|
94
|
+
background-color: #2563eb;
|
|
95
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Styles
|
|
3
|
+
* Cross-theme compatible styles for light/dark mode
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Theme Cards - Glass morphism effect */
|
|
7
|
+
.theme-card {
|
|
8
|
+
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
9
|
+
backdrop-filter: blur(10px) !important;
|
|
10
|
+
color: #111827 !important;
|
|
11
|
+
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
12
|
+
border-radius: 10px !important;
|
|
13
|
+
transition: all 0.2s ease;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
html.dark .theme-card {
|
|
17
|
+
background-color: rgba(31, 41, 55, 0.2) !important;
|
|
18
|
+
backdrop-filter: blur(10px) !important;
|
|
19
|
+
color: white !important;
|
|
20
|
+
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
21
|
+
border-radius: 10px !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Theme Text */
|
|
25
|
+
.theme-text {
|
|
26
|
+
color: #111827 !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
html.dark .theme-text {
|
|
30
|
+
color: white !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Theme Borders */
|
|
34
|
+
.theme-border {
|
|
35
|
+
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
html.dark .theme-border {
|
|
39
|
+
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Universal Card Borders - Transparent cross-theme */
|
|
43
|
+
.card-border,
|
|
44
|
+
.border-base-200,
|
|
45
|
+
[class*="border-base-200"],
|
|
46
|
+
[class*="dark:border-base-700"] {
|
|
47
|
+
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
html.dark .card-border,
|
|
51
|
+
html.dark .border-base-200,
|
|
52
|
+
html.dark [class*="border-base-200"],
|
|
53
|
+
html.dark [class*="dark:border-base-700"] {
|
|
54
|
+
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Icon Spacing Defaults */
|
|
58
|
+
.theme-card .material-icons:not(.no-margin) {
|
|
59
|
+
margin-right: 0.75rem !important; /* 12px default spacing */
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.theme-card .flex.items-center .material-icons {
|
|
63
|
+
margin-right: 0.75rem !important;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.theme-card .status-badge .material-icons {
|
|
67
|
+
margin-right: 0.25rem !important; /* 4px for badges */
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Icon spacing for buttons and links */
|
|
71
|
+
.theme-card button .material-icons,
|
|
72
|
+
.theme-card a .material-icons {
|
|
73
|
+
margin-right: 0.5rem !important;
|
|
74
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Tracker Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* GitHub-style heatmap visualization for activity data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function activityTrackerComponent(activityData) {
|
|
8
|
+
return {
|
|
9
|
+
activityData: activityData || [],
|
|
10
|
+
weeks: [],
|
|
11
|
+
|
|
12
|
+
init() {
|
|
13
|
+
this.processData();
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
processData() {
|
|
17
|
+
if (!this.activityData || this.activityData.length === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Group days into weeks (7 days per column)
|
|
22
|
+
this.weeks = [];
|
|
23
|
+
for (let i = 0; i < this.activityData.length; i += 7) {
|
|
24
|
+
this.weeks.push(this.activityData.slice(i, i + 7));
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
getCellColor(count) {
|
|
29
|
+
if (count === 0) {
|
|
30
|
+
return 'bg-gray-200 dark:bg-gray-700';
|
|
31
|
+
} else if (count <= 2) {
|
|
32
|
+
return 'bg-green-200 dark:bg-green-800';
|
|
33
|
+
} else if (count <= 5) {
|
|
34
|
+
return 'bg-green-400 dark:bg-green-600';
|
|
35
|
+
} else if (count <= 10) {
|
|
36
|
+
return 'bg-green-600 dark:bg-green-500';
|
|
37
|
+
} else {
|
|
38
|
+
return 'bg-green-800 dark:bg-green-400';
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
getCellTitle(day) {
|
|
43
|
+
return `${day.date}: ${day.count} activities`;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
get hasData() {
|
|
47
|
+
return this.activityData && this.activityData.length > 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Register component
|
|
53
|
+
document.addEventListener('alpine:init', () => {
|
|
54
|
+
Alpine.data('activityTracker', activityTrackerComponent);
|
|
55
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* Universal Chart.js wrapper for Alpine
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function chartComponent(chartData, chartType = 'line', options = {}) {
|
|
8
|
+
return {
|
|
9
|
+
chart: null,
|
|
10
|
+
chartData: chartData,
|
|
11
|
+
chartType: chartType,
|
|
12
|
+
|
|
13
|
+
init() {
|
|
14
|
+
this.$nextTick(() => {
|
|
15
|
+
this.renderChart();
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
renderChart() {
|
|
20
|
+
const canvas = this.$refs.canvas;
|
|
21
|
+
|
|
22
|
+
if (!canvas || typeof Chart === 'undefined') {
|
|
23
|
+
console.error('Chart.js not loaded or canvas not found');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Default options
|
|
29
|
+
const defaultOptions = {
|
|
30
|
+
responsive: true,
|
|
31
|
+
maintainAspectRatio: false,
|
|
32
|
+
plugins: {
|
|
33
|
+
legend: {
|
|
34
|
+
display: true,
|
|
35
|
+
position: 'top'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
scales: {
|
|
39
|
+
y: {
|
|
40
|
+
beginAtZero: true,
|
|
41
|
+
ticks: {
|
|
42
|
+
precision: 0
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Merge with custom options
|
|
49
|
+
const mergedOptions = this.deepMerge(defaultOptions, options);
|
|
50
|
+
|
|
51
|
+
this.chart = new Chart(canvas, {
|
|
52
|
+
type: this.chartType,
|
|
53
|
+
data: this.chartData,
|
|
54
|
+
options: mergedOptions
|
|
55
|
+
});
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('Error creating chart:', error);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
updateChart(newData) {
|
|
62
|
+
if (this.chart) {
|
|
63
|
+
this.chart.data = newData;
|
|
64
|
+
this.chart.update();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
destroy() {
|
|
69
|
+
if (this.chart) {
|
|
70
|
+
this.chart.destroy();
|
|
71
|
+
this.chart = null;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
deepMerge(target, source) {
|
|
76
|
+
const output = Object.assign({}, target);
|
|
77
|
+
if (this.isObject(target) && this.isObject(source)) {
|
|
78
|
+
Object.keys(source).forEach(key => {
|
|
79
|
+
if (this.isObject(source[key])) {
|
|
80
|
+
if (!(key in target))
|
|
81
|
+
Object.assign(output, { [key]: source[key] });
|
|
82
|
+
else
|
|
83
|
+
output[key] = this.deepMerge(target[key], source[key]);
|
|
84
|
+
} else {
|
|
85
|
+
Object.assign(output, { [key]: source[key] });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return output;
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
isObject(item) {
|
|
93
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Register component
|
|
99
|
+
document.addEventListener('alpine:init', () => {
|
|
100
|
+
Alpine.data('chart', chartComponent);
|
|
101
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Modal Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* Manages command execution modal with tabs for output and documentation
|
|
5
|
+
* Requires: /static/admin/js/utils.js for getCookie function
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
function commandModalComponent() {
|
|
9
|
+
return {
|
|
10
|
+
open: false,
|
|
11
|
+
commandName: '',
|
|
12
|
+
activeTab: 'output',
|
|
13
|
+
outputHtml: '',
|
|
14
|
+
docsHtml: '<p class="text-font-subtle-light dark:text-font-subtle-dark">Loading documentation...</p>',
|
|
15
|
+
statusText: 'Executing...',
|
|
16
|
+
statusClass: 'bg-yellow-500 animate-pulse',
|
|
17
|
+
|
|
18
|
+
async execute(commandName) {
|
|
19
|
+
this.commandName = commandName;
|
|
20
|
+
this.open = true;
|
|
21
|
+
this.activeTab = 'output';
|
|
22
|
+
this.outputHtml = '';
|
|
23
|
+
this.statusText = 'Executing...';
|
|
24
|
+
this.statusClass = 'bg-yellow-500 animate-pulse';
|
|
25
|
+
|
|
26
|
+
// Load documentation
|
|
27
|
+
this.loadDocumentation(commandName);
|
|
28
|
+
|
|
29
|
+
// Execute command
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch('/cfg/commands/execute/', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
'X-CSRFToken': window.getCookie('csrftoken')
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
command: commandName,
|
|
39
|
+
args: [],
|
|
40
|
+
options: {}
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const reader = response.body.getReader();
|
|
49
|
+
const decoder = new TextDecoder();
|
|
50
|
+
|
|
51
|
+
while (true) {
|
|
52
|
+
const {done, value} = await reader.read();
|
|
53
|
+
if (done) break;
|
|
54
|
+
|
|
55
|
+
const chunk = decoder.decode(value);
|
|
56
|
+
const lines = chunk.split('\n');
|
|
57
|
+
|
|
58
|
+
lines.forEach(line => {
|
|
59
|
+
if (line.startsWith('data: ')) {
|
|
60
|
+
try {
|
|
61
|
+
const data = JSON.parse(line.slice(6));
|
|
62
|
+
this.handleCommandData(data);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('Error parsing command data:', e);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('Error executing command:', error);
|
|
71
|
+
this.outputHtml += `\n❌ Error: ${error.message}`;
|
|
72
|
+
this.statusText = 'Error';
|
|
73
|
+
this.statusClass = 'bg-red-500';
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
handleCommandData(data) {
|
|
78
|
+
switch (data.type) {
|
|
79
|
+
case 'start':
|
|
80
|
+
this.outputHtml = `🚀 Starting command: ${data.command}\n📝 Arguments: ${data.args.join(' ')}\n\n`;
|
|
81
|
+
this.statusText = 'Executing...';
|
|
82
|
+
this.statusClass = 'bg-yellow-500 animate-pulse';
|
|
83
|
+
break;
|
|
84
|
+
case 'output':
|
|
85
|
+
this.outputHtml += data.line + '\n';
|
|
86
|
+
break;
|
|
87
|
+
case 'complete':
|
|
88
|
+
const success = data.return_code === 0;
|
|
89
|
+
this.statusText = success ? 'Completed' : 'Failed';
|
|
90
|
+
this.statusClass = success ? 'bg-green-500' : 'bg-red-500';
|
|
91
|
+
let completionMessage = `${success ? '✅' : '❌'} Command completed with exit code: ${data.return_code}`;
|
|
92
|
+
if (data.execution_time) {
|
|
93
|
+
completionMessage += ` (${data.execution_time}s)`;
|
|
94
|
+
}
|
|
95
|
+
this.outputHtml += '\n' + completionMessage;
|
|
96
|
+
break;
|
|
97
|
+
case 'error':
|
|
98
|
+
this.outputHtml += `❌ Error: ${data.error}\n`;
|
|
99
|
+
this.statusText = 'Error';
|
|
100
|
+
this.statusClass = 'bg-red-500';
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async loadDocumentation(commandName) {
|
|
106
|
+
this.docsHtml = `
|
|
107
|
+
<div class="space-y-4">
|
|
108
|
+
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark">${commandName}</h3>
|
|
109
|
+
<div class="text-sm text-font-subtle-light dark:text-font-subtle-dark">
|
|
110
|
+
<p class="mb-2">Loading documentation for <code class="bg-base-200 dark:bg-base-700 px-2 py-1 rounded">${commandName}</code>...</p>
|
|
111
|
+
<p class="mt-4">To view full documentation, run:</p>
|
|
112
|
+
<pre class="bg-base-200 dark:bg-base-700 p-3 rounded-lg mt-2"><code>python manage.py help ${commandName}</code></pre>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(`/cfg/commands/help/${commandName}/`);
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
if (data.help_text) {
|
|
121
|
+
this.docsHtml = `
|
|
122
|
+
<div class="space-y-4">
|
|
123
|
+
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark">${commandName}</h3>
|
|
124
|
+
<pre class="text-sm text-font-default-light dark:text-font-default-dark whitespace-pre-wrap">${data.help_text}</pre>
|
|
125
|
+
</div>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Error loading documentation:', error);
|
|
130
|
+
this.docsHtml = `
|
|
131
|
+
<div class="text-red-600 dark:text-red-400">
|
|
132
|
+
<p>Failed to load documentation.</p>
|
|
133
|
+
<p class="text-sm mt-2">Try running: <code class="bg-base-200 dark:bg-base-700 px-2 py-1 rounded">python manage.py help ${commandName}</code></p>
|
|
134
|
+
</div>
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
close() {
|
|
140
|
+
this.open = false;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Register component when Alpine initializes
|
|
146
|
+
document.addEventListener('alpine:init', () => {
|
|
147
|
+
Alpine.data('commandModal', commandModalComponent);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Global wrapper for backward compatibility
|
|
151
|
+
window.executeCommand = function(commandName) {
|
|
152
|
+
const modalEl = document.querySelector('[x-data="commandModal"]');
|
|
153
|
+
if (modalEl && Alpine) {
|
|
154
|
+
const component = Alpine.$data(modalEl);
|
|
155
|
+
if (component && component.execute) {
|
|
156
|
+
component.execute(commandName);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands Panel Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* Manages command search, filtering, and category toggling
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function commandsPanelComponent(totalCommands) {
|
|
8
|
+
return {
|
|
9
|
+
searchQuery: '',
|
|
10
|
+
totalCommands: totalCommands,
|
|
11
|
+
visibleCommands: totalCommands,
|
|
12
|
+
expandedCategories: new Set(),
|
|
13
|
+
|
|
14
|
+
init() {
|
|
15
|
+
// Keyboard shortcuts
|
|
16
|
+
document.addEventListener('keydown', (e) => {
|
|
17
|
+
// Focus search with Ctrl+F or Cmd+F
|
|
18
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
this.$refs.searchInput?.focus();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Clear search with Escape
|
|
24
|
+
if (e.key === 'Escape' && document.activeElement === this.$refs.searchInput) {
|
|
25
|
+
this.clearSearch();
|
|
26
|
+
this.$refs.searchInput?.blur();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
search() {
|
|
32
|
+
const query = this.searchQuery.toLowerCase().trim();
|
|
33
|
+
let visibleCount = 0;
|
|
34
|
+
|
|
35
|
+
// Get all categories
|
|
36
|
+
const categories = document.querySelectorAll('[id^="content-"]');
|
|
37
|
+
|
|
38
|
+
categories.forEach(category => {
|
|
39
|
+
const categoryName = category.id.replace('content-', '');
|
|
40
|
+
const commands = category.querySelectorAll('.command-item');
|
|
41
|
+
let categoryHasVisibleCommands = false;
|
|
42
|
+
|
|
43
|
+
commands.forEach(command => {
|
|
44
|
+
const commandName = command.querySelector('.command-name')?.textContent.toLowerCase() || '';
|
|
45
|
+
const commandDesc = command.querySelector('.command-description')?.textContent.toLowerCase() || '';
|
|
46
|
+
|
|
47
|
+
if (!query || commandName.includes(query) || commandDesc.includes(query)) {
|
|
48
|
+
command.style.display = 'block';
|
|
49
|
+
categoryHasVisibleCommands = true;
|
|
50
|
+
visibleCount++;
|
|
51
|
+
} else {
|
|
52
|
+
command.style.display = 'none';
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Show/hide category based on whether it has visible commands
|
|
57
|
+
const categoryHeader = document.querySelector(`button[data-category="${categoryName}"]`);
|
|
58
|
+
const categoryContainer = categoryHeader?.parentElement;
|
|
59
|
+
|
|
60
|
+
if (categoryContainer) {
|
|
61
|
+
if (categoryHasVisibleCommands) {
|
|
62
|
+
categoryContainer.style.display = 'block';
|
|
63
|
+
|
|
64
|
+
// Auto-expand categories when searching
|
|
65
|
+
if (query) {
|
|
66
|
+
this.expandedCategories.add(categoryName);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
categoryContainer.style.display = 'none';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.visibleCommands = visibleCount;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
clearSearch() {
|
|
78
|
+
this.searchQuery = '';
|
|
79
|
+
this.visibleCommands = this.totalCommands;
|
|
80
|
+
|
|
81
|
+
// Show all commands and categories
|
|
82
|
+
const categories = document.querySelectorAll('[id^="content-"]');
|
|
83
|
+
const allCommands = document.querySelectorAll('.command-item');
|
|
84
|
+
|
|
85
|
+
categories.forEach(category => {
|
|
86
|
+
const categoryName = category.id.replace('content-', '');
|
|
87
|
+
const categoryHeader = document.querySelector(`button[data-category="${categoryName}"]`);
|
|
88
|
+
const categoryContainer = categoryHeader?.parentElement;
|
|
89
|
+
|
|
90
|
+
if (categoryContainer) {
|
|
91
|
+
categoryContainer.style.display = 'block';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Collapse all categories
|
|
95
|
+
this.expandedCategories.delete(categoryName);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
allCommands.forEach(command => {
|
|
99
|
+
command.style.display = 'block';
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
toggleCategory(categoryName) {
|
|
104
|
+
if (this.expandedCategories.has(categoryName)) {
|
|
105
|
+
this.expandedCategories.delete(categoryName);
|
|
106
|
+
} else {
|
|
107
|
+
this.expandedCategories.add(categoryName);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
isCategoryExpanded(categoryName) {
|
|
112
|
+
return this.expandedCategories.has(categoryName);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
get showNoResults() {
|
|
116
|
+
return this.visibleCommands === 0 && this.searchQuery.trim() !== '';
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
get showClearButton() {
|
|
120
|
+
return this.searchQuery.trim() !== '';
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Register component when Alpine initializes
|
|
126
|
+
document.addEventListener('alpine:init', () => {
|
|
127
|
+
Alpine.data('commandsPanel', commandsPanelComponent);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Global wrapper for backward compatibility
|
|
131
|
+
window.toggleCategory = function(category) {
|
|
132
|
+
const panel = document.querySelector('[x-data*="commandsPanel"]');
|
|
133
|
+
if (panel && Alpine) {
|
|
134
|
+
const component = Alpine.$data(panel);
|
|
135
|
+
if (component && component.toggleCategory) {
|
|
136
|
+
component.toggleCategory(category);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|