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
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands Section Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* Manages command execution, modals, search, and category toggling
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function commandsSectionComponent(totalCommands) {
|
|
8
|
+
return {
|
|
9
|
+
// State
|
|
10
|
+
totalCommands: totalCommands,
|
|
11
|
+
visibleCommands: totalCommands,
|
|
12
|
+
searchQuery: '',
|
|
13
|
+
expandedCategories: new Set(),
|
|
14
|
+
|
|
15
|
+
// Modals
|
|
16
|
+
showConfirmModal: false,
|
|
17
|
+
showOutputModal: false,
|
|
18
|
+
|
|
19
|
+
// Current command
|
|
20
|
+
currentCommand: '',
|
|
21
|
+
currentCommandApp: '',
|
|
22
|
+
currentCommandDescription: '',
|
|
23
|
+
|
|
24
|
+
// Output
|
|
25
|
+
commandOutput: '',
|
|
26
|
+
commandStatus: 'idle', // idle, running, success, error
|
|
27
|
+
statusText: '',
|
|
28
|
+
statusClass: '',
|
|
29
|
+
|
|
30
|
+
init() {
|
|
31
|
+
// Keyboard shortcuts
|
|
32
|
+
document.addEventListener('keydown', (e) => {
|
|
33
|
+
// ESC to close modals
|
|
34
|
+
if (e.key === 'Escape') {
|
|
35
|
+
this.showConfirmModal = false;
|
|
36
|
+
this.showOutputModal = false;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Search functionality
|
|
42
|
+
search() {
|
|
43
|
+
const query = this.searchQuery.toLowerCase().trim();
|
|
44
|
+
let visibleCount = 0;
|
|
45
|
+
|
|
46
|
+
// Get all categories
|
|
47
|
+
const categories = document.querySelectorAll('.category-block');
|
|
48
|
+
|
|
49
|
+
categories.forEach(categoryBlock => {
|
|
50
|
+
const commands = categoryBlock.querySelectorAll('.command-item');
|
|
51
|
+
let categoryHasVisibleCommands = false;
|
|
52
|
+
|
|
53
|
+
commands.forEach(command => {
|
|
54
|
+
const commandName = command.querySelector('.command-name')?.textContent.toLowerCase() || '';
|
|
55
|
+
const commandDesc = command.dataset.description?.toLowerCase() || '';
|
|
56
|
+
|
|
57
|
+
if (!query || commandName.includes(query) || commandDesc.includes(query)) {
|
|
58
|
+
command.style.display = 'block';
|
|
59
|
+
categoryHasVisibleCommands = true;
|
|
60
|
+
visibleCount++;
|
|
61
|
+
} else {
|
|
62
|
+
command.style.display = 'none';
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Show/hide category
|
|
67
|
+
if (categoryHasVisibleCommands) {
|
|
68
|
+
categoryBlock.style.display = 'block';
|
|
69
|
+
|
|
70
|
+
// Auto-expand when searching
|
|
71
|
+
if (query) {
|
|
72
|
+
const category = categoryBlock.querySelector('.category-toggle')?.dataset.category;
|
|
73
|
+
if (category) {
|
|
74
|
+
this.expandedCategories.add(category);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
categoryBlock.style.display = 'none';
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.visibleCommands = visibleCount;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
clearSearch() {
|
|
86
|
+
this.searchQuery = '';
|
|
87
|
+
this.visibleCommands = this.totalCommands;
|
|
88
|
+
|
|
89
|
+
// Show all
|
|
90
|
+
document.querySelectorAll('.category-block').forEach(block => {
|
|
91
|
+
block.style.display = 'block';
|
|
92
|
+
});
|
|
93
|
+
document.querySelectorAll('.command-item').forEach(item => {
|
|
94
|
+
item.style.display = 'block';
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Collapse all
|
|
98
|
+
this.expandedCategories.clear();
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// Category toggle
|
|
102
|
+
toggleCategory(category) {
|
|
103
|
+
if (this.expandedCategories.has(category)) {
|
|
104
|
+
this.expandedCategories.delete(category);
|
|
105
|
+
} else {
|
|
106
|
+
this.expandedCategories.add(category);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
isCategoryExpanded(category) {
|
|
111
|
+
return this.expandedCategories.has(category);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// Command confirmation
|
|
115
|
+
confirmCommand(commandName, commandApp, commandDesc) {
|
|
116
|
+
this.currentCommand = commandName;
|
|
117
|
+
this.currentCommandApp = commandApp || '';
|
|
118
|
+
this.currentCommandDescription = commandDesc || '';
|
|
119
|
+
this.showConfirmModal = true;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
cancelExecution() {
|
|
123
|
+
this.showConfirmModal = false;
|
|
124
|
+
this.currentCommand = '';
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// Command execution
|
|
128
|
+
async executeCommand() {
|
|
129
|
+
this.showConfirmModal = false;
|
|
130
|
+
this.showOutputModal = true;
|
|
131
|
+
this.commandOutput = '';
|
|
132
|
+
this.commandStatus = 'running';
|
|
133
|
+
this.statusText = 'Executing...';
|
|
134
|
+
this.statusClass = 'bg-yellow-500 animate-pulse';
|
|
135
|
+
|
|
136
|
+
const commandName = this.currentCommand;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const response = await fetch('/cfg/commands/execute/', {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: {
|
|
142
|
+
'Content-Type': 'application/json',
|
|
143
|
+
'X-CSRFToken': window.getCookie('csrftoken')
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify({
|
|
146
|
+
command: commandName,
|
|
147
|
+
args: [],
|
|
148
|
+
options: {}
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const reader = response.body.getReader();
|
|
157
|
+
const decoder = new TextDecoder();
|
|
158
|
+
|
|
159
|
+
while (true) {
|
|
160
|
+
const {done, value} = await reader.read();
|
|
161
|
+
if (done) break;
|
|
162
|
+
|
|
163
|
+
const chunk = decoder.decode(value);
|
|
164
|
+
const lines = chunk.split('\n');
|
|
165
|
+
|
|
166
|
+
lines.forEach(line => {
|
|
167
|
+
if (line.startsWith('data: ')) {
|
|
168
|
+
try {
|
|
169
|
+
const data = JSON.parse(line.slice(6));
|
|
170
|
+
this.handleCommandData(data);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error('Error parsing SSE data:', e);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Error executing command:', error);
|
|
179
|
+
this.addOutput(`\n❌ Error: ${error.message}`, 'error');
|
|
180
|
+
this.commandStatus = 'error';
|
|
181
|
+
this.statusText = 'Error';
|
|
182
|
+
this.statusClass = 'bg-red-500';
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
handleCommandData(data) {
|
|
187
|
+
switch (data.type) {
|
|
188
|
+
case 'start':
|
|
189
|
+
this.addOutput(`🚀 Starting command: ${data.command}\n📝 Arguments: ${data.args.join(' ')}\n\n`, 'info');
|
|
190
|
+
this.commandStatus = 'running';
|
|
191
|
+
this.statusText = 'Executing...';
|
|
192
|
+
this.statusClass = 'bg-yellow-500 animate-pulse';
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case 'output':
|
|
196
|
+
this.addOutput(data.line + '\n', 'output');
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case 'complete':
|
|
200
|
+
const success = data.return_code === 0;
|
|
201
|
+
this.commandStatus = success ? 'success' : 'error';
|
|
202
|
+
this.statusText = success ? 'Completed' : 'Failed';
|
|
203
|
+
this.statusClass = success ? 'bg-green-500' : 'bg-red-500';
|
|
204
|
+
|
|
205
|
+
let message = `${success ? '✅' : '❌'} Command completed with exit code: ${data.return_code}`;
|
|
206
|
+
if (data.execution_time) {
|
|
207
|
+
message += ` (${data.execution_time}s)`;
|
|
208
|
+
}
|
|
209
|
+
this.addOutput('\n' + message, success ? 'success' : 'error');
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
case 'error':
|
|
213
|
+
this.addOutput(`❌ Error: ${data.error}\n`, 'error');
|
|
214
|
+
this.commandStatus = 'error';
|
|
215
|
+
this.statusText = 'Error';
|
|
216
|
+
this.statusClass = 'bg-red-500';
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Auto-scroll
|
|
221
|
+
this.$nextTick(() => {
|
|
222
|
+
const outputEl = this.$refs.commandOutput;
|
|
223
|
+
if (outputEl) {
|
|
224
|
+
outputEl.scrollTop = outputEl.scrollHeight;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
addOutput(text, type = 'output') {
|
|
230
|
+
this.commandOutput += text;
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
closeOutputModal() {
|
|
234
|
+
this.showOutputModal = false;
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
copyOutput() {
|
|
238
|
+
navigator.clipboard.writeText(this.commandOutput).then(() => {
|
|
239
|
+
// Could add a toast notification here
|
|
240
|
+
console.log('Output copied to clipboard');
|
|
241
|
+
}).catch(err => {
|
|
242
|
+
console.error('Failed to copy:', err);
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
get showClearButton() {
|
|
248
|
+
return this.searchQuery.trim() !== '';
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
get showNoResults() {
|
|
252
|
+
return this.visibleCommands === 0 && this.searchQuery.trim() !== '';
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Register component
|
|
258
|
+
document.addEventListener('alpine:init', () => {
|
|
259
|
+
Alpine.data('commandsSection', commandsSectionComponent);
|
|
260
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Tabs Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* Manages dashboard tabs with URL hash navigation and browser history
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function dashboardTabsComponent() {
|
|
8
|
+
return {
|
|
9
|
+
activeTab: 0,
|
|
10
|
+
tabNames: ['overview', 'zones', 'users', 'system', 'stats', 'app-stats', 'commands', 'widgets'],
|
|
11
|
+
|
|
12
|
+
init() {
|
|
13
|
+
// Initialize from URL hash or default to first tab
|
|
14
|
+
this.activeTab = this.getTabFromHash();
|
|
15
|
+
|
|
16
|
+
// Handle browser back/forward buttons
|
|
17
|
+
window.addEventListener('hashchange', () => {
|
|
18
|
+
this.activeTab = this.getTabFromHash();
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
switchTab(index) {
|
|
23
|
+
this.activeTab = index;
|
|
24
|
+
|
|
25
|
+
// Update URL hash
|
|
26
|
+
if (this.tabNames[index]) {
|
|
27
|
+
history.replaceState(null, null, '#' + this.tabNames[index]);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
getTabFromHash() {
|
|
32
|
+
const hash = window.location.hash.substring(1); // Remove #
|
|
33
|
+
const tabIndex = this.tabNames.indexOf(hash);
|
|
34
|
+
return tabIndex >= 0 ? tabIndex : 0; // Default to first tab
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
isActive(index) {
|
|
38
|
+
return this.activeTab === index;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Register component when Alpine initializes
|
|
44
|
+
document.addEventListener('alpine:init', () => {
|
|
45
|
+
Alpine.data('dashboardTabs', dashboardTabsComponent);
|
|
46
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Metrics Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive width styling for health percentage bars
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function systemMetricsComponent(metricsData) {
|
|
8
|
+
return {
|
|
9
|
+
metrics: metricsData || {},
|
|
10
|
+
|
|
11
|
+
getBarStyle(percentage) {
|
|
12
|
+
return `width: ${percentage}%`;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Register component
|
|
18
|
+
document.addEventListener('alpine:init', () => {
|
|
19
|
+
Alpine.data('systemMetrics', systemMetricsComponent);
|
|
20
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle Section Alpine.js Component
|
|
3
|
+
*
|
|
4
|
+
* Simple collapsible section toggle
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function toggleSectionComponent() {
|
|
8
|
+
return {
|
|
9
|
+
expandedSections: new Set(),
|
|
10
|
+
|
|
11
|
+
toggleSection(sectionId) {
|
|
12
|
+
if (this.expandedSections.has(sectionId)) {
|
|
13
|
+
this.expandedSections.delete(sectionId);
|
|
14
|
+
} else {
|
|
15
|
+
this.expandedSections.add(sectionId);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
isSectionExpanded(sectionId) {
|
|
20
|
+
return this.expandedSections.has(sectionId);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Register component
|
|
26
|
+
document.addEventListener('alpine:init', () => {
|
|
27
|
+
Alpine.data('toggleSection', toggleSectionComponent);
|
|
28
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Django CFG Utility Functions
|
|
3
|
+
*
|
|
4
|
+
* Modern ES6+ utility functions for the admin interface
|
|
5
|
+
* These are minimal utilities that don't require Alpine.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get CSRF token from cookies
|
|
10
|
+
* @param {string} name - Cookie name (usually 'csrftoken')
|
|
11
|
+
* @returns {string|null} Cookie value or null if not found
|
|
12
|
+
*/
|
|
13
|
+
function getCookie(name) {
|
|
14
|
+
if (!document.cookie) return null;
|
|
15
|
+
|
|
16
|
+
const cookies = document.cookie.split(';');
|
|
17
|
+
for (const cookie of cookies) {
|
|
18
|
+
const [cookieName, cookieValue] = cookie.trim().split('=');
|
|
19
|
+
if (cookieName === name) {
|
|
20
|
+
return decodeURIComponent(cookieValue);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Copy text to clipboard with visual feedback
|
|
28
|
+
* @param {Event} event - Click event from button
|
|
29
|
+
* @param {string} text - Text to copy
|
|
30
|
+
*/
|
|
31
|
+
async function copyToClipboard(event, text) {
|
|
32
|
+
try {
|
|
33
|
+
await navigator.clipboard.writeText(text);
|
|
34
|
+
|
|
35
|
+
const button = event?.target?.closest('button');
|
|
36
|
+
if (!button) return;
|
|
37
|
+
|
|
38
|
+
// Store original state
|
|
39
|
+
const originalHTML = button.innerHTML;
|
|
40
|
+
const originalClasses = button.className;
|
|
41
|
+
|
|
42
|
+
// Show success state
|
|
43
|
+
button.innerHTML = '<span class="material-icons text-xs mr-1">check</span>Copied';
|
|
44
|
+
button.className = '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';
|
|
45
|
+
|
|
46
|
+
// Reset after 2 seconds
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
button.innerHTML = originalHTML;
|
|
49
|
+
button.className = originalClasses;
|
|
50
|
+
}, 2000);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('Failed to copy to clipboard:', err);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Export to window for global access
|
|
57
|
+
if (typeof window !== 'undefined') {
|
|
58
|
+
window.getCookie = getCookie;
|
|
59
|
+
window.copyToClipboard = copyToClipboard;
|
|
60
|
+
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
{% endif %}
|
|
30
30
|
|
|
31
31
|
{% if closable %}
|
|
32
|
-
<button
|
|
32
|
+
<button @click="{{ close_function|default:'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">
|
|
33
33
|
<span class="material-icons">close</span>
|
|
34
34
|
</button>
|
|
35
35
|
{% endif %}
|
|
@@ -8,48 +8,9 @@
|
|
|
8
8
|
{{ block.super }}
|
|
9
9
|
{{ media.js }}
|
|
10
10
|
<script type="text/javascript" src="{% static 'admin/js/constance.js' %}"></script>
|
|
11
|
-
|
|
12
|
-
<!--
|
|
13
|
-
<
|
|
14
|
-
/* Smooth transitions for all interactive elements */
|
|
15
|
-
.constance-expandable {
|
|
16
|
-
transition: all 0.2s ease-in-out;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/* Better hover states */
|
|
20
|
-
.constance tr:hover {
|
|
21
|
-
background-color: rgba(0, 0, 0, 0.02);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.dark .constance tr:hover {
|
|
25
|
-
background-color: rgba(255, 255, 255, 0.02);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* Improved focus states for accessibility */
|
|
29
|
-
.constance a:focus,
|
|
30
|
-
.constance button:focus {
|
|
31
|
-
outline: 2px solid rgb(59, 130, 246);
|
|
32
|
-
outline-offset: 2px;
|
|
33
|
-
border-radius: 4px;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/* Better spacing for long text */
|
|
37
|
-
.constance .break-words {
|
|
38
|
-
word-break: break-word;
|
|
39
|
-
overflow-wrap: break-word;
|
|
40
|
-
hyphens: auto;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/* Smooth animations for expand/collapse */
|
|
44
|
-
.constance [x-show] {
|
|
45
|
-
transition: opacity 0.2s ease-in-out;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* Icon rotation animation */
|
|
49
|
-
.constance .material-symbols-outlined {
|
|
50
|
-
transition: transform 0.3s ease-in-out;
|
|
51
|
-
}
|
|
52
|
-
</style>
|
|
11
|
+
|
|
12
|
+
<!-- Constance UI Styles -->
|
|
13
|
+
<link rel="stylesheet" href="{% static 'admin/css/constance.css' %}"}
|
|
53
14
|
{% endblock %}
|
|
54
15
|
|
|
55
16
|
{% block content %}
|
|
@@ -71,14 +71,6 @@
|
|
|
71
71
|
{% endif %}
|
|
72
72
|
{% endblock %}
|
|
73
73
|
|
|
74
|
-
{% block documentation_tab %}
|
|
75
|
-
{% if documentation_section %}
|
|
76
|
-
{{ documentation_section|safe }}
|
|
77
|
-
{% else %}
|
|
78
|
-
{% include 'admin/snippets/tabs/documentation_tab.html' %}
|
|
79
|
-
{% endif %}
|
|
80
|
-
{% endblock %}
|
|
81
|
-
|
|
82
74
|
{% block widgets_tab %}
|
|
83
75
|
{% if widgets_section %}
|
|
84
76
|
{{ widgets_section|safe }}
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
|
|
56
56
|
{% block footer %}
|
|
57
57
|
{{ block.super }}
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
|
|
59
|
+
<!-- Utils.js is loaded in dashboard_with_tabs.html extrahead -->
|
|
60
|
+
<!-- All legacy Vanilla JS files have been migrated to Alpine.js components -->
|
|
61
|
+
<!-- Old files removed: dashboard.js, commands.js -->
|
|
60
62
|
{% endblock %}
|