// קובץ: 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 = ['HX630', 'VX630', 'SR630', 'SR635', 'SR645', 'SR570', 'SR530', 'SE350', 'SR250', '1U'];
const is1U = models1U_List.some(m => upperModel.includes(m));
const showDisks = isVx || window.isVsanMode || window.isNutanixMode;
// 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) {
const effSpan = rackSummaryEl.querySelector('span[title]') || rackSummaryEl.querySelector('span');
if (effSpan && /TB/i.test(effSpan.innerText)) {
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 sol = (typeof window.currentSolution !== 'undefined' && window.currentSolution) ? window.currentSolution : null;
const coresPerNode = sol ? sol.coresPerNode : (coresPerCpu * 2);
const nodeCountEff = sol ? sol.nodes : nodeCount;
const ramPerNodeEff = sol ? sol.ramPerNode : ramPerNode;
// Same overhead model as sizer UI (CVM / hypervisor — not a flat 1.2 factor)
const isNutanix = !!window.isNutanixMode;
const esxiEl = document.getElementById('overhead_esxi');
const ramPctEl = document.getElementById('overhead_ram_pct');
const cpuPctEl = document.getElementById('overhead_cpu_pct');
const cvmProfileEl = document.getElementById('nutanix_cvm_profile');
const hypervisorRamOverhead = esxiEl ? (parseFloat(esxiEl.value) || 12) : 12;
const ramOverheadPct = ramPctEl ? (parseFloat(ramPctEl.value) / 100) : 0;
const cpuOverheadPct = cpuPctEl ? (parseFloat(cpuPctEl.value) / 100) : 0;
let cvmRamOverhead = 0;
let cvmCpuOverhead = 0;
if (isNutanix) {
const cvmProfiles = { typical: { ram: 32, cpu: 12 }, high: { ram: 64, cpu: 12 } };
const selectedProfile = cvmProfileEl ? cvmProfileEl.value : 'typical';
const profile = cvmProfiles[selectedProfile] || cvmProfiles.typical;
cvmRamOverhead = profile.ram;
cvmCpuOverhead = profile.cpu;
}
const variableRamOverheadVal = Math.round(ramPerNodeEff * ramOverheadPct);
const systemOverheadRam = isNutanix
? (cvmRamOverhead + 4)
: (hypervisorRamOverhead + variableRamOverheadVal);
const systemOverheadCpu = isNutanix
? (cvmCpuOverhead + 1)
: (coresPerNode * cpuOverheadPct);
// Prefer solution totals (include growth) — same source as rack UI
const totalRamNeeded = sol ? sol.totalRamNeeded : rawTotalRam;
const totalCoresNeeded = sol
? sol.totalCoresNeeded
: (rawPhyCoresNeeded > 0 ? rawPhyCoresNeeded : (rawTotalVCpu / 4));
const vmRamPerNode = totalRamNeeded / Math.max(nodeCountEff, 1);
const usedCoresPerNode = totalCoresNeeded / Math.max(nodeCountEff, 1);
const ramDisplayVal = Math.min(ramPerNodeEff, Math.round(vmRamPerNode + systemOverheadRam));
const coresDisplayVal = Math.min(coresPerNode, Math.ceil(usedCoresPerNode + systemOverheadCpu));
const cpuFreq = sol && sol.cpu && sol.cpu.freq
? sol.cpu.freq
: (parseFloat((cpuString.match(/@\s*([\d.]+)\s*GHz/i) || [])[1]) || 0);
const growthEl = document.getElementById('growth_buffer');
const yearsEl = document.getElementById('growth_years');
const growthRate = growthEl ? (parseInt(growthEl.value) / 100) : 0;
const years = yearsEl ? parseInt(yearsEl.value) : 3;
const growthFactor = Math.pow(1 + growthRate, years);
const peakGhzInput = document.getElementById('peak_ghz_demand');
const peakGhzDemand = peakGhzInput && peakGhzInput.value ? parseFloat(peakGhzInput.value) : 0;
const finalGhzNeeded = peakGhzDemand * growthFactor;
const ghzPerNode = coresPerNode * cpuFreq;
const demandGhzPerNode = finalGhzNeeded > 0
? (finalGhzNeeded / Math.max(nodeCountEff, 1))
: (usedCoresPerNode * cpuFreq);
const overheadGhzPerNode = systemOverheadCpu * cpuFreq;
const ghzDisplayVal = Math.min(ghzPerNode, demandGhzPerNode + overheadGhzPerNode);
let cpuBarPct = Math.min(100, (coresDisplayVal / coresPerNode) * 100).toFixed(0);
let ramBarPct = Math.min(100, (ramDisplayVal / ramPerNodeEff) * 100).toFixed(0);
let ghzBarPct = ghzPerNode > 0 ? Math.min(100, (ghzDisplayVal / ghzPerNode) * 100).toFixed(0) : 0;
if (cpuBarPct < 5) cpuBarPct = 5;
if (ramBarPct < 5) ramBarPct = 5;
if (ghzBarPct < 5 && ghzDisplayVal > 0) ghzBarPct = 5;
const ohLabel = isNutanix
? `CVM ${Math.round(systemOverheadRam)}GB / ${Math.round(systemOverheadCpu)} vCPU`
: `O/H ${Math.round(systemOverheadRam)}GB / ${Math.round(systemOverheadCpu)} cores`;
const cpuBarText = `Cores ${coresDisplayVal} / ${coresPerNode} (${cpuBarPct}%)`;
const ramBarText = `GB ${ramDisplayVal} / ${ramPerNodeEff} (${ramBarPct}%)`;
const ghzBarText = ghzPerNode > 0
? `GHz ${ghzDisplayVal.toLocaleString(undefined, {maximumFractionDigits: 1})} / ${ghzPerNode.toLocaleString(undefined, {maximumFractionDigits: 1})} (${ghzBarPct}%)`
: 'GHz -';
nodeStatsHtml = `
Per Node Specs
${cpuString}
${ramPerNodeEff} GB RAM
Estimated Node Load (Avg + System O/H)
${ohLabel}
`;
// *** BOM Summary - UPDATED to 6 Columns & Include Storage ***
bomSummaryHtml = `
SERVER MODEL
${nodeCountEff}x ${selectedModel}
PROCESSOR / NODE
${cpuString}
MEMORY / NODE
${ramPerNodeEff} GB
TOTAL CORES
${nodeCountEff * coresPerNode}
TOTAL STORAGE
${totalStorageDisplay}
${showDisks ? '(Usable)' : '(Raw)'}
TOTAL CLUSTER RAM
${(ramPerNodeEff * nodeCountEff).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();
}