// קובץ: 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)
${rows}
WORKLOADVMsvCPU (Total)RAM (Total)RATIO
`; } 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)
${manualRowsHtml}
WORKLOADVMsvCPU (Total)RAM (Total)DISK (Total)RATIO
`; } } } // 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)
${manualRowsHtml}
WORKLOADVMsvCPU (Total)RAM (Total)DISK (Total)RATIO
`; } } } // 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}
${ramBarText}
RAM
${cpuBarText}
CPU
${ghzBarText}
GHz
`; // *** 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 += `
NODE ${i+1}
${disksHtml}
`; }); } // HTML Output const printWin = window.open('', '_blank', 'width=1200,height=1200'); if (!printWin) { alert("Popups blocked!"); return; } const finalHtml = ` ${document.title}
INFRASTRUCTURE SIZING ASSESSMENT

${clientName}

${projectName}

${fullDateDisplay}
Prepared by: ${preparedBy}
Infrastructure Snapshot
${snapshotHtml}
${finalGapSection} ${finalClusterSection}
Proposed Architecture (BOM)
${bomSummaryHtml}
${nodeStatsHtml}
${rackHtml}
`; printWin.document.write(finalHtml); printWin.document.close(); }