// קובץ: 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)
${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 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)
${ramBarText}
RAM
${cpuBarText}
CPU
`; // *** 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 += `
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(); }