// קובץ: dc_print_logic.js
// גרסה: Final v3 - With Rack Disk Summary Logic
function printCleanReport() {
// 1. שליפת נתונים בסיסיים
const clientName = document.getElementById('client_name').value || "Client";
const projectName = document.getElementById('project_name').value || "Project";
const prepByInput = document.getElementById('prepared_by');
const preparedBy = (prepByInput && prepByInput.value) ? prepByInput.value : "Lenovo Data Center Group";
// 2. זמן ישראל
const now = new Date();
const options = { timeZone: 'Asia/Jerusalem', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false };
const formatter = new Intl.DateTimeFormat('en-GB', options);
const parts = formatter.formatToParts(now);
const getPart = (type) => parts.find(p => p.type === type).value;
const fullDateDisplay = `${getPart('day')}/${getPart('month')}/${getPart('year')}, ${getPart('hour')}:${getPart('minute')}`;
const fileTimestamp = `${getPart('year')}-${getPart('month')}-${getPart('day')}_${getPart('hour')}-${getPart('minute')}`;
const safeClient = clientName.replace(/\s+/g, '_');
const safeProject = projectName.replace(/\s+/g, '_');
document.title = `Sizing_${safeProject}_${safeClient}_${fileTimestamp}`;
// 3. זיהוי דגם שרת
const serverBlades = document.querySelectorAll('.server-blade');
let rawModelText = "Generic Server";
if (serverBlades.length > 0) {
const fullText = serverBlades[0].innerText;
const match = fullText.match(/Node \d+\s+([^\n]+)/);
if (match) rawModelText = match[1].replace(/^2x\s+/, '').trim();
}
const filterEl = document.getElementById('server_model_filter');
let selectedModel = (filterEl && filterEl.value !== 'all') ? filterEl.value : rawModelText;
const upperModel = selectedModel.toUpperCase();
const isVx = upperModel.includes('VX');
const models1U_List = ['VX630', 'SR630', 'SR635', 'SR645', 'SR570', 'SR530', 'SE350', 'SR250', '1U'];
const is1U = models1U_List.some(m => upperModel.includes(m));
const showDisks = isVx || (document.querySelector('input[name="storage_mode"]:checked')?.value === 'vsan');
// 4. Snapshot Data
const getVal = (id) => {
const el = document.getElementById(id);
return el ? parseFloat(el.innerText.replace(/,/g, '')) : 0;
};
const getValStr = (id) => { const el = document.getElementById(id); return el ? el.innerText.trim() : '-'; };
const rawTotalVCpu = getVal('sum_vcpu');
const rawTotalRam = getVal('sum_ram');
const rawPhyCoresNeeded = getVal('sum_phy_cores_needed');
// --- לוגיקה חדשה לשליפת נפח אחסון ---
// ניסיון לשלוף מהאלמנט המיוחד של Rack Disk Summary
let totalStorageDisplay = getValStr('sum_storage_top'); // ברירת מחדל
const rackSummaryEl = document.getElementById('rack_disk_summary');
if (rackSummaryEl) {
// חיפוש ה-SPAN הספציפי עם ה-Title המתאים
const effSpan = rackSummaryEl.querySelector('span[title="Effective Usable Capacity"]');
if (effSpan) {
totalStorageDisplay = effSpan.innerText.trim();
}
}
const snapshotData = [
{ label: "VMs", current: getValStr('sum_vms'), proj: getValStr('proj_vms') },
{ label: "TOTAL vCPU", current: getValStr('sum_vcpu'), proj: getValStr('proj_vcpu') },
{ label: "TOTAL RAM (GB)", current: getValStr('sum_ram'), proj: getValStr('proj_ram') },
{ label: "USABLE STORAGE", current: totalStorageDisplay, proj: getValStr('proj_storage') },
{ label: "PHYSICAL CORES", current: getValStr('sum_phy_cores_needed'), proj: getValStr('proj_phy_cores') },
{ label: "GHz DEMAND", current: getValStr('sum_ghz_needed'), proj: "Target" }
];
let snapshotHtml = '';
snapshotData.forEach(item => {
let projText = item.proj;
if (item.label !== "GHz DEMAND") projText = `Proj: ${item.proj}`;
snapshotHtml += `
${item.current}
${item.label}
${projText}
`;
});
// 5. Workload Summary (Logical)
let finalWorkloadSection = '';
const hasImportData = window.importedClusters && window.importedClusters.length > 0;
const manualContainer = document.getElementById('workloads_container');
if (hasImportData) {
let rows = '';
window.importedClusters.forEach(c => {
const vms = c.count || 0;
const avgVcpu = c.vcpu || 0;
const avgRam = c.ram || 0;
const totalVcpu = (avgVcpu * vms);
const totalRam = (avgRam * vms);
rows += `| ${c.name || "Cluster"} | ${vms} | ${totalVcpu.toFixed(0)} ${avgVcpu} per VM | ${totalRam.toFixed(0)} ${avgRam} GB per VM | ... | - |
`;
});
finalWorkloadSection = `Workload Summary (Logical Allocation)
| WORKLOAD | VMs | vCPU (Total) | RAM (Total) | RATIO |
${rows}
`;
}
else if (manualContainer) {
const rows = manualContainer.querySelectorAll('.workload-row');
if (rows.length > 0) {
let manualRowsHtml = '';
let validManual = false;
rows.forEach(row => {
const name = row.querySelector('.row-name')?.value || "Manual Workload";
const count = parseInt(row.querySelector('.row-count')?.value || 0);
const vcpu = parseInt(row.querySelector('.row-vcpu')?.value || 0);
const ram = parseInt(row.querySelector('.row-ram')?.value || 0);
const storage = parseInt(row.querySelector('.row-storage')?.value || 0);
if (count > 0) {
validManual = true;
manualRowsHtml += `| ${name} | ${count} | ${(count*vcpu)} ${vcpu} per VM | ${(count*ram)} ${ram} GB per VM | ${(count*storage).toLocaleString()} ${storage} GB per VM | 1:- |
`;
}
});
if (validManual) {
finalWorkloadSection = `Workload Summary (Manual Input)
| WORKLOAD | VMs | vCPU (Total) | RAM (Total) | DISK (Total) | RATIO |
${manualRowsHtml}
`;
}
}
}
// Gap Analysis (Fixed)
let finalGapSection = '';
const gapEl = document.getElementById('global_inventory_content');
if (gapEl && gapEl.innerText.trim().length > 20) {
finalGapSection = `Gap Analysis (Current vs Required)
${gapEl.innerHTML}
`;
}
// 6. Unified Cluster Analysis (The Super-Card)
let finalClusterSection = '';
if (hasImportData) {
let cardsHtml = '';
window.importedClusters.forEach(c => {
const phy = c.cluster_stats.phy_stats || {};
const hostCount = phy.host_count || 0;
const totalPhyCores = phy.total_phy_cores || 0;
const totalPhyRam = phy.total_phy_ram_gb ? Math.round(phy.total_phy_ram_gb) : 1;
const peakCpu = c.cluster_stats.peak_cpu_ghz.toFixed(0);
const totalCpuGhz = phy.total_phy_ghz ? phy.total_phy_ghz.toFixed(0) : 0;
const cpuPct = (totalCpuGhz > 0) ? ((peakCpu / totalCpuGhz) * 100).toFixed(1) : 0;
const peakRam = (c.cluster_stats.peak_ram_mib / 1024).toFixed(0);
const ramPct = (totalPhyRam > 1) ? ((peakRam / totalPhyRam) * 100).toFixed(1) : 0;
const vms = c.count || 0;
const avgVcpu = c.vcpu || 0;
const totalVcpu = (avgVcpu * vms);
let ratio = "-";
if (totalPhyCores > 0 && totalVcpu > 0) {
ratio = (totalVcpu / totalPhyCores).toFixed(1) + ":1";
}
let statusIcon = '';
let statusTitle = 'Balanced';
let statusDesc = 'Optimal load.';
const maxUtil = Math.max(parseFloat(cpuPct), parseFloat(ramPct));
if (maxUtil >= 80) {
statusIcon = '';
statusTitle = 'Stressed';
statusDesc = `High load (${maxUtil.toFixed(1)}%). High growth factors required.`;
} else if (maxUtil <= 40) {
statusIcon = '';
statusTitle = 'Oversized';
statusDesc = `Low utilization (${maxUtil.toFixed(1)}%). Consider lower growth factors.`;
}
cardsHtml += `
${c.name}
${hostCount} Hosts
${totalPhyCores} Phy. Cores
${vms}
VMs
Ratio: ${ratio}
CPU
${peakCpu} / ${totalCpuGhz} GHz
RAM
${peakRam} / ${totalPhyRam} GB
${statusIcon}
${statusTitle}
${statusDesc}
`;
});
finalClusterSection = `Cluster Analysis (Physical & Logical)
${cardsHtml}
`;
}
else if (manualContainer) {
const rows = manualContainer.querySelectorAll('.workload-row');
if (rows.length > 0) {
let manualRowsHtml = '';
rows.forEach(row => {
const name = row.querySelector('.row-name')?.value || "Manual Workload";
const count = parseInt(row.querySelector('.row-count')?.value || 0);
const vcpu = parseInt(row.querySelector('.row-vcpu')?.value || 0);
const ram = parseInt(row.querySelector('.row-ram')?.value || 0);
const storage = parseInt(row.querySelector('.row-storage')?.value || 0);
if (count > 0) {
manualRowsHtml += `| ${name} | ${count} | ${(count*vcpu)} ${vcpu} per VM | ${(count*ram)} ${ram} GB per VM | ${(count*storage).toLocaleString()} ${storage} GB per VM | 1:- |
`;
}
});
if (manualRowsHtml) {
finalClusterSection = `Workload Summary (Manual Input)
| WORKLOAD | VMs | vCPU (Total) | RAM (Total) | DISK (Total) | RATIO |
${manualRowsHtml}
`;
}
}
}
// 7. BOM & Node Stats
let rackHtml = '';
let bomSummaryHtml = '';
let nodeStatsHtml = '';
if (serverBlades.length > 0) {
const fullTextOne = serverBlades[0].innerText;
const nodeCount = serverBlades.length;
const cpuMatch = fullTextOne.match(/2x\s+([^\n]+)/);
const cpuString = cpuMatch ? "2x " + cpuMatch[1].trim() : "2x CPU";
const ramMatch = fullTextOne.match(/(\d{2,})\s*GB/i);
const ramPerNode = ramMatch ? parseInt(ramMatch[1]) : 0;
const coresMatch = cpuString.match(/\((\d+)C/i);
const coresPerCpu = coresMatch ? parseInt(coresMatch[1]) : 16;
const coresPerNode = coresPerCpu * 2;
const overheadFactor = 1.2;
// Stats Calculation
const ramUsedRaw = rawTotalRam / nodeCount;
const ramUsedWithOverhead = ramUsedRaw * overheadFactor;
const ramDisplayVal = Math.min(ramPerNode, Math.ceil(ramUsedWithOverhead));
let coresUsedRaw = 0;
if (rawPhyCoresNeeded > 0) {
coresUsedRaw = rawPhyCoresNeeded / nodeCount;
} else {
coresUsedRaw = (rawTotalVCpu / 4) / nodeCount;
}
const coresUsedWithOverhead = coresUsedRaw * overheadFactor;
const coresDisplayVal = Math.min(coresPerNode, Math.ceil(coresUsedWithOverhead));
let cpuBarPct = Math.min(100, (coresDisplayVal / coresPerNode) * 100).toFixed(0);
let ramBarPct = Math.min(100, (ramDisplayVal / ramPerNode) * 100).toFixed(0);
if (cpuBarPct < 5) cpuBarPct = 5;
if (ramBarPct < 5) ramBarPct = 5;
const cpuBarText = `Cores ${coresDisplayVal} / ${coresPerNode} (${cpuBarPct}%)`;
const ramBarText = `GB ${ramDisplayVal} / ${ramPerNode} (${ramBarPct}%)`;
nodeStatsHtml = `
Per Node Specs
${cpuString}
${ramPerNode} GB RAM
Estimated Node Load (Avg + Overhead)
`;
// *** BOM Summary - UPDATED to 6 Columns & Include Storage ***
bomSummaryHtml = `
SERVER MODEL
${nodeCount}x ${selectedModel}
PROCESSOR / NODE
${cpuString}
MEMORY / NODE
${ramPerNode} GB
TOTAL CORES
${nodeCount * coresPerNode}
TOTAL STORAGE
${totalStorageDisplay}
${showDisks ? '(Usable)' : '(Raw)'}
TOTAL CLUSTER RAM
${(ramPerNode * nodeCount).toLocaleString()} GB
`;
const chassisClass = is1U ? 'u1' : 'u2';
const maxSlots = is1U ? 10 : 24;
serverBlades.forEach((srv, i) => {
let disksHtml = '';
if (showDisks) {
let diskCount = 7;
const diskMatch = srv.innerText.match(/(\d+)\s*\/\s*\d+\s*Disks/i);
if (diskMatch) diskCount = parseInt(diskMatch[1]);
for(let j=0; j < maxSlots; j++) {
const activeClass = (j < diskCount) ? 'active' : '';
disksHtml += ``;
}
}
rackHtml += `
`;
});
}
// HTML Output
const printWin = window.open('', '_blank', 'width=1200,height=1200');
if (!printWin) { alert("Popups blocked!"); return; }
const finalHtml = `
${document.title}
Infrastructure Snapshot
${snapshotHtml}
${finalGapSection}
${finalClusterSection}
Proposed Architecture (BOM)
${bomSummaryHtml}
${nodeStatsHtml}
${rackHtml}
`;
printWin.document.write(finalHtml);
printWin.document.close();
}