django-cfg 1.4.74__py3-none-any.whl → 1.4.76__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- 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/api/health/drf_views.py +27 -0
- 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 +9 -0
- django_cfg/management/commands/check_endpoints.py +1 -1
- django_cfg/middleware/authentication.py +27 -0
- 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.74.dist-info → django_cfg-1.4.76.dist-info}/METADATA +1 -1
- {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/RECORD +49 -41
- 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.74.dist-info → django_cfg-1.4.76.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.74.dist-info → django_cfg-1.4.76.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,178 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Django CFG Dashboard Styles
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Dashboard-specific styling
|
|
5
|
+
*
|
|
6
|
+
* NOTE: Common styles moved to separate files:
|
|
7
|
+
* - Tab styles: /static/admin/css/tabs.css
|
|
8
|
+
* - Theme styles: /static/admin/css/theme.css
|
|
9
|
+
* - Layout (including .overview-grid): /static/admin/css/layout.css
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
/* =============================================================================
|
|
8
|
-
Dashboard Tab Styles
|
|
9
|
-
============================================================================= */
|
|
10
|
-
|
|
11
|
-
.tab-content {
|
|
12
|
-
display: none;
|
|
13
|
-
animation: fadeIn 0.3s ease-in-out;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.tab-content.active {
|
|
17
|
-
display: block;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#dashboard-tabs button {
|
|
21
|
-
cursor: pointer;
|
|
22
|
-
transition: all 0.2s ease-in-out;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@keyframes fadeIn {
|
|
26
|
-
from {
|
|
27
|
-
opacity: 0;
|
|
28
|
-
transform: translateY(10px);
|
|
29
|
-
}
|
|
30
|
-
to {
|
|
31
|
-
opacity: 1;
|
|
32
|
-
transform: translateY(0);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/* =============================================================================
|
|
37
|
-
Theme Cards - Cross-theme Compatible
|
|
38
|
-
============================================================================= */
|
|
39
|
-
|
|
40
|
-
.theme-card {
|
|
41
|
-
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
42
|
-
backdrop-filter: blur(10px) !important;
|
|
43
|
-
color: #111827 !important;
|
|
44
|
-
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
45
|
-
border-radius: 10px !important;
|
|
46
|
-
transition: all 0.2s ease;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
html.dark .theme-card {
|
|
50
|
-
background-color: rgba(31, 41, 55, 0.2) !important;
|
|
51
|
-
backdrop-filter: blur(10px) !important;
|
|
52
|
-
color: white !important;
|
|
53
|
-
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
54
|
-
border-radius: 10px !important;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.theme-text {
|
|
58
|
-
color: #111827 !important;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
html.dark .theme-text {
|
|
62
|
-
color: white !important;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.theme-border {
|
|
66
|
-
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
html.dark .theme-border {
|
|
70
|
-
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/* =============================================================================
|
|
74
|
-
Grid Layout
|
|
75
|
-
============================================================================= */
|
|
76
|
-
|
|
77
|
-
.overview-grid {
|
|
78
|
-
display: grid !important;
|
|
79
|
-
grid-template-columns: 1fr !important;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@media (min-width: 1280px) {
|
|
83
|
-
.overview-grid {
|
|
84
|
-
grid-template-columns: 2fr 1fr !important;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* =============================================================================
|
|
89
|
-
Tab Navigation Styling
|
|
90
|
-
============================================================================= */
|
|
91
|
-
|
|
92
|
-
#dashboard-tabs {
|
|
93
|
-
border-bottom: 2px solid #e5e7eb !important;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
html.dark #dashboard-tabs {
|
|
97
|
-
border-bottom-color: #374151 !important;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
#dashboard-tabs button {
|
|
101
|
-
background-color: #f3f4f6 !important;
|
|
102
|
-
color: #6b7280 !important;
|
|
103
|
-
border-bottom: 2px solid transparent !important;
|
|
104
|
-
transition: all 0.2s ease !important;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
html.dark #dashboard-tabs button {
|
|
108
|
-
background-color: #374151 !important;
|
|
109
|
-
color: #9ca3af !important;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#dashboard-tabs button:hover {
|
|
113
|
-
background-color: #e5e7eb !important;
|
|
114
|
-
color: #374151 !important;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
html.dark #dashboard-tabs button:hover {
|
|
118
|
-
background-color: #4b5563 !important;
|
|
119
|
-
color: #d1d5db !important;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/* Active tab */
|
|
123
|
-
#dashboard-tabs button.active,
|
|
124
|
-
#dashboard-tabs button[class*="bg-blue"] {
|
|
125
|
-
background-color: #2563eb !important;
|
|
126
|
-
color: white !important;
|
|
127
|
-
border-bottom-color: #2563eb !important;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
html.dark #dashboard-tabs button.active,
|
|
131
|
-
html.dark #dashboard-tabs button[class*="bg-blue"] {
|
|
132
|
-
background-color: #3b82f6 !important;
|
|
133
|
-
color: white !important;
|
|
134
|
-
border-bottom-color: #3b82f6 !important;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/* =============================================================================
|
|
138
|
-
Universal Card Borders - Transparent Cross-theme
|
|
139
|
-
============================================================================= */
|
|
140
|
-
|
|
141
|
-
.card-border,
|
|
142
|
-
.border-base-200,
|
|
143
|
-
[class*="border-base-200"],
|
|
144
|
-
[class*="dark:border-base-700"] {
|
|
145
|
-
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
html.dark .card-border,
|
|
149
|
-
html.dark .border-base-200,
|
|
150
|
-
html.dark [class*="border-base-200"],
|
|
151
|
-
html.dark [class*="dark:border-base-700"] {
|
|
152
|
-
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/* =============================================================================
|
|
156
|
-
Icon Spacing Defaults
|
|
157
|
-
============================================================================= */
|
|
158
|
-
|
|
159
|
-
.theme-card .material-icons:not(.no-margin) {
|
|
160
|
-
margin-right: 0.75rem !important; /* 12px default spacing */
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.theme-card .flex.items-center .material-icons {
|
|
164
|
-
margin-right: 0.75rem !important;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.theme-card .status-badge .material-icons {
|
|
168
|
-
margin-right: 0.25rem !important; /* 4px for badges */
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.theme-card button .material-icons,
|
|
172
|
-
.theme-card a .material-icons {
|
|
173
|
-
margin-right: 0.5rem !important;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
12
|
/* =============================================================================
|
|
177
13
|
Section Specific Styles
|
|
178
14
|
============================================================================= */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Styles
|
|
3
|
+
* Grid layouts, containers, and responsive design
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Overview Grid Layout */
|
|
7
|
+
.overview-grid {
|
|
8
|
+
display: grid !important;
|
|
9
|
+
grid-template-columns: 1fr !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@media (min-width: 1280px) {
|
|
13
|
+
.overview-grid {
|
|
14
|
+
grid-template-columns: 2fr 1fr !important;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Alpine.js Cloak - Hide elements until Alpine initializes */
|
|
19
|
+
[x-cloak] {
|
|
20
|
+
display: none !important;
|
|
21
|
+
}
|
|
@@ -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
|
+
};
|