Firmware Update Log Template

Firmware Update Log Template

Device & Project Metadata

Log New Update

Log History (Editable)

# Version Date Changes/Notes Tester Status Act.

Review the final log and summary statistics.

Summary Statistics

Full Log Preview

Generate the log to see the export preview.

No update data logged to generate statistics.

'; return; } const statsMap = { 'Total Updates': stats.count, 'Latest Version': stats.currentVersion, 'Passed Tests': stats.passed, 'Failed Tests': stats.failed, 'Success Rate (%)': stats.successRate + '%' }; for (const label in statsMap) { const card = document.createElement('div'); card.className = 'fu-stat-card'; card.innerHTML = `
${statsMap[label]}
${label}
`; statsOutput.appendChild(card); } generatePreview(); }; // --- Summary & Download Functions (Tab 3) --- const getReportData = () => { return { setup: { project: inputs.project.value, model: inputs.model.value, initialVersion: inputs.initialVersion.value, devTeam: inputs.devTeam.value }, log: firmwareLog, stats: calculateStats() }; }; const generatePreview = () => { const data = getReportData(); const stats = data.stats; const tableRowsHTML = data.log.map((log, index) => ` ${index + 1} ${escapeHTML(log.newVersion)} ${escapeHTML(log.date)} ${escapeHTML(log.changes)} ${escapeHTML(log.tester)} ${escapeHTML(log.status)} `).join(''); summaryPreview.innerHTML = `

Firmware Log: ${escapeHTML(data.setup.project)}

Model: ${escapeHTML(data.setup.model)} | Team: ${escapeHTML(data.setup.devTeam)}

1. Summary Statistics

${stats ? `

Initial Version: ${escapeHTML(data.setup.initialVersion)}

Latest Version: ${escapeHTML(stats.currentVersion)}

Total Updates: ${stats.count}

Success Rate: ${stats.successRate}%

` : `

No data available for statistics.

`}

2. Full Update History

${tableRowsHTML.length > 0 ? tableRowsHTML : ''}
# Version Date Changes/Notes Tester Status
No updates logged.
`; }; const downloadTxt = () => { const data = getReportData(); const stats = data.stats; let content = `FIRMWARE UPDATE LOG: ${data.setup.project.toUpperCase()}\n`; content += `========================================================\n`; content += `Model: ${data.setup.model}\nTeam: ${data.setup.devTeam}\n`; content += `Initial Version: ${data.setup.initialVersion}\n\n`; if (stats) { content += "SUMMARY STATISTICS\n"; content += `Total Updates Logged: ${stats.count}\nLatest Version: ${stats.currentVersion}\nSuccess Rate: ${stats.successRate}%\n\n`; } content += "UPDATE HISTORY\n"; content += "--------------------------------------------------------\n"; content += " # | Version | Date\t\t| Tester\t| Status\t| Changes/Notes\n"; content += "--------------------------------------------------------\n"; data.log.forEach((log, index) => { content += `${(index + 1).toString().padStart(2)} | ${log.newVersion.padEnd(7)} | ${log.date}\t| ${log.tester.padEnd(10)}\t| ${log.status.padEnd(7)}\t| ${log.changes}\n`; }); const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `${data.setup.project.replace(/ /g, '_')}_firmware_log.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(a.href); }; const downloadPDF = () => { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined' || typeof window.jspdf.autoTable === 'undefined') { alert('Error: jsPDF libraries not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF('l', 'mm', 'a4'); // Landscape for wide table const data = getReportData(); const stats = data.stats; const margin = 10; const pageWidth = doc.internal.pageSize.getWidth(); let yPos = 15; // --- PDF Helper --- const addSectionTitle = (title) => { doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.setTextColor(0, 188, 212); // Teal doc.text(title, margin, yPos); doc.setDrawColor(224, 224, 224); doc.line(margin, yPos + 1, pageWidth - margin, yPos + 1); yPos += 8; }; // 1. Header doc.setFontSize(20); doc.setFont(undefined, 'bold'); doc.setTextColor(44, 62, 80); doc.text(`Firmware Update Log: ${data.setup.project}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 8; doc.setFontSize(10); doc.setFont(undefined, 'normal'); doc.setTextColor(108, 117, 125); doc.text(`Model: ${data.setup.model} | Team: ${data.setup.devTeam} | Initial Version: ${data.setup.initialVersion}`, pageWidth / 2, yPos, { align: 'center' }); yPos += 10; // 2. Summary Statistics if (stats) { addSectionTitle("1. Summary Statistics"); const statsHead = [['Initial Version', 'Latest Version', 'Total Updates', 'Passed Tests', 'Success Rate (%)']]; const statsBody = [[ data.setup.initialVersion, stats.currentVersion, stats.count, stats.passed, `${stats.successRate}%` ]]; doc.autoTable({ startY: yPos, head: statsHead, body: statsBody, theme: 'grid', styles: { fontSize: 10, cellPadding: 3, textColor: [52, 73, 94], halign: 'center' }, headStyles: { fillColor: [0, 188, 212], textColor: [255, 255, 255] }, margin: { left: margin, right: margin } }); yPos = doc.autoTable.previous.finalY + 10; } // 3. Raw Log Table addSectionTitle("2. Full Update History"); const logHead = [['#', 'Version', 'Date', 'Changes/Notes', 'Tester', 'Status']]; const logBody = data.log.map((log, index) => [ index + 1, log.newVersion, log.date, log.changes, log.tester, log.status ]); doc.autoTable({ startY: yPos, head: logHead, body: logBody, theme: 'grid', styles: { fontSize: 9, cellPadding: 2, textColor: [52, 73, 94], valign: 'top' }, headStyles: { fillColor: [230, 230, 230], textColor: [44, 62, 80] }, columnStyles: { 0: { cellWidth: 8, halign: 'center' }, 3: { cellWidth: 80 } }, // Give notes more space margin: { left: margin, right: margin } }); doc.save(`${data.setup.project.replace(/ /g, '_')}_firmware_log.pdf`); }; // --- Event Listeners --- // Global Navigation tabButtons.forEach((btn, index) => { btn.addEventListener('click', () => showTab(index + 1)); }); nextBtn.addEventListener('click', () => showTab(currentTab + 1)); prevBtn.addEventListener('click', () => showTab(currentTab - 1)); // Tab 2 Actions (Add, Remove, Edit) newUpdateInputs.addBtn.addEventListener('click', addUpdate); updatesTbody.addEventListener('click', (e) => { if (e.target.dataset.removeId) { removeUpdate(parseInt(e.target.dataset.removeId)); } }); updatesTbody.addEventListener('blur', (e) => { if (e.target.tagName === 'TD' && e.target.isContentEditable) { const id = parseInt(e.target.dataset.id); const field = e.target.dataset.field; updateUpdate(id, field, e.target.textContent); } }, true); // Tab 3 Actions refreshBtn.addEventListener('click', calculateAndRenderStats); downloadPdfBtn.addEventListener('click', downloadPDF); downloadTxtBtn.addEventListener('click', downloadTxt); // --- Initialization --- newUpdateInputs.date.value = new Date().toISOString().substring(0, 10); showTab(1); // Set initial state });
Scroll to Top