Refactor dashboard data serialization and mock context for improved clarity
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:
2026-02-12 20:10:09 +03:00
parent 76eae52d2a
commit 656a6bfac4
8 changed files with 313 additions and 90 deletions

View File

@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
};
// --- 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;

View File

@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
// 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 };