Refactor dashboard data serialization and mock context for improved clarity
Some checks failed
CI / ci (push) Has been cancelled
Some checks failed
CI / ci (push) Has been cancelled
- Introduced `serialize_audit_for_response` and `serialize_current_cluster_for_template` functions to handle JSON serialization of audit and cluster data, enhancing data consistency for API responses and template rendering. - Updated `get_mock_context` in `mock_data.py` to utilize the new serialization functions, simplifying the mock data structure and improving readability. - Refactored `collect_context` and `collect_audits` in `views.py` to leverage the new serialization methods, ensuring a cleaner and more maintainable codebase. - Added unit tests for the new serialization functions to ensure correctness and reliability of data formatting.
This commit is contained in:
@@ -1,11 +1,29 @@
|
||||
/**
|
||||
* Dashboard logic: stats rendering, audit selector, CPU chart, migration table.
|
||||
* Expects globals: SKELETON_MODE, CURRENT_CLUSTER, auditData (set by index.html).
|
||||
* Depends on: utils.js (formatBytes, getCSSVar, calculateStats)
|
||||
*
|
||||
* Expected globals (set by index.html / inline script):
|
||||
* - SKELETON_MODE (boolean): whether to fetch data from API instead of using embedded context
|
||||
* - CURRENT_CLUSTER: { host_labels, cpu_current } for "current" cluster chart when no audits
|
||||
* - auditData: object keyed by audit id, each value { name, migrations, hostData: { labels, current, projected } }
|
||||
* - INITIAL_AUDIT_ID: first audit id to select when not in skeleton mode
|
||||
*
|
||||
* Required DOM element ids:
|
||||
* - auditSelector, previewCpu, previewRam, previewScope, previewStrategy
|
||||
* - regionBadge, auditsCount, migrationTableBody, migrationCount, cpuDistributionChart
|
||||
* - currentCpuMean, currentCpuStd, currentCpuStdBlock
|
||||
* - elements with data-stats="..." for renderStats()
|
||||
*
|
||||
* Depends on: utils.js (formatBytes, getCSSVar, calculateStats, escapeHtml, formatAuditDate)
|
||||
*/
|
||||
(function() {
|
||||
var cpuDistributionChart = null;
|
||||
var escapeHtml = typeof window.escapeHtml === 'function' ? window.escapeHtml : function(text) {
|
||||
if (text == null) return '';
|
||||
var s = String(text);
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
};
|
||||
|
||||
// --- Initialization: audit selector change (preview panel) ---
|
||||
document.getElementById('auditSelector').addEventListener('change', function(e) {
|
||||
var option = this.options[this.selectedIndex];
|
||||
if (!option) return;
|
||||
@@ -15,6 +33,7 @@
|
||||
document.getElementById('previewStrategy').textContent = option.dataset.strategy || 'Balanced';
|
||||
});
|
||||
|
||||
// --- Stats: setStat, setProgress, renderStats ---
|
||||
function setStat(key, text) {
|
||||
document.querySelectorAll('[data-stats="' + key + '"]').forEach(function(el) {
|
||||
el.textContent = text;
|
||||
@@ -85,6 +104,7 @@
|
||||
document.querySelectorAll('[data-stats]').forEach(function(n) { n.classList.remove('animate-pulse'); });
|
||||
}
|
||||
|
||||
// --- Audits: renderAudits, loadSelectedAudit ---
|
||||
function renderAudits(auditsList) {
|
||||
if (!auditsList || !auditsList.length) {
|
||||
var countEl = document.getElementById('auditsCount');
|
||||
@@ -117,7 +137,7 @@
|
||||
opt.setAttribute('data-scope', audit.scope || 'Full Cluster');
|
||||
opt.setAttribute('data-strategy', audit.strategy || 'Balanced');
|
||||
opt.setAttribute('data-goal', audit.goal || '');
|
||||
var dateStr = audit.created_at ? new Date(audit.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : '';
|
||||
var dateStr = formatAuditDate(audit.created_at);
|
||||
opt.textContent = audit.name + ' (' + dateStr + ')';
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
@@ -136,6 +156,7 @@
|
||||
updateCPUCharts(auditId);
|
||||
};
|
||||
|
||||
// --- Migration table: updateMigrationTable ---
|
||||
function updateMigrationTable(auditId) {
|
||||
var tbody = document.getElementById('migrationTableBody');
|
||||
var migrationCount = document.getElementById('migrationCount');
|
||||
@@ -151,12 +172,13 @@
|
||||
data.migrations.forEach(function(migration) {
|
||||
var impact = migration.impact || 'Low';
|
||||
var impactClass = { 'Low': 'badge-success', 'Medium': 'badge-warning', 'High': 'badge-error' }[impact] || 'badge-neutral';
|
||||
html += '<tr><td class="font-medium"><div>' + migration.instanceName + '</div></td><td><div class="flex items-center gap-2"><span class="badge badge-outline badge-xs">' + migration.source + '</span><svg class="w-3 h-3 text-base-content/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/></svg><span class="badge badge-primary badge-outline badge-xs">' + migration.destination + '</span></div></td><td><code class="text-xs bg-base-200 px-2 py-1 rounded">' + migration.flavor + '</code></td><td><span class="badge ' + impactClass + ' badge-xs">' + impact + '</span></td></tr>';
|
||||
html += '<tr><td class="font-medium"><div>' + escapeHtml(migration.instanceName) + '</div></td><td><div class="flex items-center gap-2"><span class="badge badge-outline badge-xs">' + escapeHtml(migration.source) + '</span><svg class="w-3 h-3 text-base-content/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/></svg><span class="badge badge-primary badge-outline badge-xs">' + escapeHtml(migration.destination) + '</span></div></td><td><code class="text-xs bg-base-200 px-2 py-1 rounded">' + escapeHtml(migration.flavor) + '</code></td><td><span class="badge ' + impactClass + ' badge-xs">' + escapeHtml(impact) + '</span></td></tr>';
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
migrationCount.textContent = data.migrations.length + ' action' + (data.migrations.length !== 1 ? 's' : '');
|
||||
}
|
||||
|
||||
// --- CPU charts: updateCPUCharts ---
|
||||
function updateCPUCharts(auditId) {
|
||||
var data = window.auditData && window.auditData[auditId];
|
||||
if (!data || !data.hostData) return;
|
||||
@@ -289,6 +311,7 @@
|
||||
: [ data.hostData.current.slice() ];
|
||||
}
|
||||
|
||||
// --- Initialization: DOMContentLoaded (skeleton vs embedded data) ---
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof SKELETON_MODE !== 'undefined' && SKELETON_MODE) {
|
||||
Promise.all([
|
||||
@@ -325,6 +348,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// --- Initialization: theme change (recreate chart) ---
|
||||
document.addEventListener('themechange', function() {
|
||||
if (cpuDistributionChart) {
|
||||
var auditId = document.getElementById('auditSelector').value;
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
// Escape for safe HTML text content (prevents XSS when inserting into HTML)
|
||||
function escapeHtml(text) {
|
||||
if (text == null) return '';
|
||||
const s = String(text);
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// Format bytes to GB (matches Django convert_bytes filter default)
|
||||
function formatBytes(bytes, targetUnit = 'GB') {
|
||||
if (bytes == null || isNaN(Number(bytes))) return '0';
|
||||
@@ -24,6 +36,16 @@ function getColorWithOpacity(className) {
|
||||
|
||||
return computedColor;
|
||||
}
|
||||
// Format audit date for display (ISO string -> short date, e.g. "Feb 1")
|
||||
function formatAuditDate(isoString) {
|
||||
if (!isoString) return '';
|
||||
try {
|
||||
return new Date(isoString).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to calculate mean and standard deviation
|
||||
function calculateStats(data) {
|
||||
if (!data || data.length === 0) return { mean: 0, std: 0 };
|
||||
|
||||
Reference in New Issue
Block a user