From 4ed185de0c39aed0c77678fe5a606a77a3d3ccb5 Mon Sep 17 00:00:00 2001 From: PaulVua Date: Tue, 10 Feb 2026 11:09:25 +0100 Subject: [PATCH] fix(ui): fix copy report and add more device info - Add system info collection at start of test (device ID, name, RTC time, GPS) - Display device info in logs header - Fix clipboard copy with fallback for non-HTTPS contexts - Use execCommand fallback for older browsers - Use ASCII-safe characters for better compatibility - Add error handling with manual copy fallback Co-Authored-By: Claude Opus 4.5 --- html/saraR4.html | 249 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 190 insertions(+), 59 deletions(-) diff --git a/html/saraR4.html b/html/saraR4.html index 3e66d21..cf56e5d 100755 --- a/html/saraR4.html +++ b/html/saraR4.html @@ -1663,6 +1663,67 @@ async function selfTestSequence() { let testsFailed = 0; try { + // Collect system info at the start + document.getElementById('selftest_status').innerHTML = ` +
+
+ Collecting system information... +
`; + + // Get system info from config + try { + const configResponse = await new Promise((resolve, reject) => { + $.ajax({ + url: 'launcher.php?type=get_config_sqlite', + dataType: 'json', + method: 'GET', + success: function(data) { resolve(data); }, + error: function(xhr, status, error) { reject(new Error(error)); } + }); + }); + + // Store in report + selfTestReport.deviceId = configResponse.deviceID || 'Unknown'; + selfTestReport.deviceName = configResponse.deviceName || 'Unknown'; + selfTestReport.modemVersion = configResponse.modem_version || 'Unknown'; + selfTestReport.latitude = configResponse.latitude_raw || 'N/A'; + selfTestReport.longitude = configResponse.longitude_raw || 'N/A'; + + // Get RTC time + try { + const rtcTime = await new Promise((resolve, reject) => { + $.ajax({ + url: 'launcher.php?type=RTC_time', + dataType: 'text', + method: 'GET', + success: function(data) { resolve(data.trim()); }, + error: function(xhr, status, error) { resolve('N/A'); } + }); + }); + selfTestReport.systemTime = rtcTime; + } catch (e) { + selfTestReport.systemTime = 'N/A'; + } + + // Log system info + addSelfTestLog('════════════════════════════════════════════════════════'); + addSelfTestLog(' NEBULEAIR PRO 4G - SELF TEST'); + addSelfTestLog('════════════════════════════════════════════════════════'); + addSelfTestLog(`Device ID: ${selfTestReport.deviceId}`); + addSelfTestLog(`Device Name: ${selfTestReport.deviceName}`); + addSelfTestLog(`Modem Version: ${selfTestReport.modemVersion}`); + addSelfTestLog(`System Time (RTC): ${selfTestReport.systemTime}`); + addSelfTestLog(`Browser Time: ${new Date().toLocaleString()}`); + addSelfTestLog(`GPS: ${selfTestReport.latitude}, ${selfTestReport.longitude}`); + addSelfTestLog('────────────────────────────────────────────────────────'); + addSelfTestLog(''); + + } catch (error) { + addSelfTestLog(`Warning: Could not get system config: ${error.message}`); + } + + await delay(300); + // Step 0: Check WiFi / Network status (informational, no pass/fail) document.getElementById('selftest_status').innerHTML = `
@@ -1978,38 +2039,43 @@ async function selfTestSequence() { function copySelfTestReport() { // Build formatted report - let report = `═══════════════════════════════════════════════════════════════ - NEBULEAIR PRO 4G - SELF TEST REPORT -═══════════════════════════════════════════════════════════════ + let report = `=============================================================== + NEBULEAIR PRO 4G - SELF TEST REPORT +=============================================================== -📅 Date: ${selfTestReport.timestamp} -🔧 Device ID: ${selfTestReport.deviceId} -📱 Modem Version: ${selfTestReport.modemVersion} +DEVICE INFORMATION +------------------ +Device ID: ${selfTestReport.deviceId || 'Unknown'} +Device Name: ${selfTestReport.deviceName || 'Unknown'} +Modem Version: ${selfTestReport.modemVersion || 'Unknown'} +System Time: ${selfTestReport.systemTime || 'Unknown'} +Report Date: ${selfTestReport.timestamp} +GPS Location: ${selfTestReport.latitude || 'N/A'}, ${selfTestReport.longitude || 'N/A'} -─────────────────────────────────────────────────────────────── - TEST RESULTS -─────────────────────────────────────────────────────────────── +=============================================================== + TEST RESULTS +=============================================================== `; // Add test results const testNames = { - wifi: '📡 WiFi/Network', - modem: '📟 Modem Connection', - sim: '💳 SIM Card', - signal: '📶 Signal Strength', - network: '🌐 Network Connection' + wifi: 'WiFi/Network', + modem: 'Modem Connection', + sim: 'SIM Card', + signal: 'Signal Strength', + network: 'Network Connection' }; for (const [testId, name] of Object.entries(testNames)) { if (selfTestReport.results[testId]) { const result = selfTestReport.results[testId]; - const statusIcon = result.status === 'Passed' ? '✅' : - result.status === 'Failed' ? '❌' : - result.status.includes('Hotspot') || result.status.includes('WiFi') || result.status.includes('Ethernet') ? 'ℹ️' : '⚠️'; - report += `${name} - Status: ${statusIcon} ${result.status} - Detail: ${result.detail} + const statusIcon = result.status === 'Passed' ? '[OK]' : + result.status === 'Failed' ? '[FAIL]' : + result.status.includes('Hotspot') || result.status.includes('WiFi') || result.status.includes('Ethernet') ? '[INFO]' : '[WARN]'; + report += `${statusIcon} ${name} + Status: ${result.status} + Detail: ${result.detail} `; } @@ -2017,67 +2083,132 @@ function copySelfTestReport() { // Add summary if (selfTestReport.summary) { - report += `─────────────────────────────────────────────────────────────── - SUMMARY -─────────────────────────────────────────────────────────────── + report += `=============================================================== + SUMMARY +=============================================================== -✅ Passed: ${selfTestReport.summary.passed} -❌ Failed: ${selfTestReport.summary.failed} -📊 Status: ${selfTestReport.summary.status} +Passed: ${selfTestReport.summary.passed} +Failed: ${selfTestReport.summary.failed} +Status: ${selfTestReport.summary.status} `; } // Add raw AT responses - report += `─────────────────────────────────────────────────────────────── - RAW AT RESPONSES -─────────────────────────────────────────────────────────────── + report += `=============================================================== + RAW AT RESPONSES +=============================================================== `; for (const [command, response] of Object.entries(selfTestReport.rawResponses)) { - report += `Command: ${command} -Response: + report += `--- ${command} --- ${response} -─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ `; } // Add full logs - report += `─────────────────────────────────────────────────────────────── - DETAILED LOGS -─────────────────────────────────────────────────────────────── + report += `=============================================================== + DETAILED LOGS +=============================================================== ${document.getElementById('selftest_logs').textContent} -═══════════════════════════════════════════════════════════════ - END OF REPORT - Generated by NebuleAir Pro 4G -═══════════════════════════════════════════════════════════════ +=============================================================== + END OF REPORT - Generated by NebuleAir Pro 4G +=============================================================== `; - // Copy to clipboard - navigator.clipboard.writeText(report).then(function() { - // Show success feedback - const copyBtn = document.getElementById('selfTestCopyBtn'); - const originalHtml = copyBtn.innerHTML; - copyBtn.innerHTML = ` - - - - Copied!`; - copyBtn.classList.remove('btn-outline-primary'); - copyBtn.classList.add('btn-success'); + // Copy to clipboard with fallback + copyToClipboard(report); +} - setTimeout(function() { - copyBtn.innerHTML = originalHtml; - copyBtn.classList.remove('btn-success'); - copyBtn.classList.add('btn-outline-primary'); - }, 2000); - }).catch(function(err) { - console.error('Failed to copy:', err); - alert('Failed to copy to clipboard. Please select and copy the logs manually.'); - }); +function copyToClipboard(text) { + const copyBtn = document.getElementById('selfTestCopyBtn'); + const originalHtml = copyBtn.innerHTML; + + // Try modern clipboard API first + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(text).then(function() { + showCopySuccess(copyBtn, originalHtml); + }).catch(function(err) { + console.error('Clipboard API failed:', err); + fallbackCopyToClipboard(text, copyBtn, originalHtml); + }); + } else { + // Fallback for non-HTTPS or older browsers + fallbackCopyToClipboard(text, copyBtn, originalHtml); + } +} + +function fallbackCopyToClipboard(text, copyBtn, originalHtml) { + // Create a temporary textarea + const textArea = document.createElement('textarea'); + textArea.value = text; + + // Make it invisible but part of the document + textArea.style.position = 'fixed'; + textArea.style.top = '0'; + textArea.style.left = '0'; + textArea.style.width = '2em'; + textArea.style.height = '2em'; + textArea.style.padding = '0'; + textArea.style.border = 'none'; + textArea.style.outline = 'none'; + textArea.style.boxShadow = 'none'; + textArea.style.background = 'transparent'; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + const successful = document.execCommand('copy'); + if (successful) { + showCopySuccess(copyBtn, originalHtml); + } else { + showCopyError(copyBtn, originalHtml); + } + } catch (err) { + console.error('Fallback copy failed:', err); + showCopyError(copyBtn, originalHtml); + } + + document.body.removeChild(textArea); +} + +function showCopySuccess(copyBtn, originalHtml) { + copyBtn.innerHTML = ' Copied!'; + copyBtn.classList.remove('btn-outline-primary'); + copyBtn.classList.add('btn-success'); + + setTimeout(function() { + copyBtn.innerHTML = originalHtml; + copyBtn.classList.remove('btn-success'); + copyBtn.classList.add('btn-outline-primary'); + }, 2000); +} + +function showCopyError(copyBtn, originalHtml) { + copyBtn.innerHTML = ' Error'; + copyBtn.classList.remove('btn-outline-primary'); + copyBtn.classList.add('btn-danger'); + + setTimeout(function() { + copyBtn.innerHTML = originalHtml; + copyBtn.classList.remove('btn-danger'); + copyBtn.classList.add('btn-outline-primary'); + }, 2000); + + // Show modal with text for manual copy + alert('Could not copy automatically. The logs are displayed below - you can select and copy them manually.'); + + // Expand the logs section + const logsCollapse = document.getElementById('selftest_logs_collapse'); + if (logsCollapse && !logsCollapse.classList.contains('show')) { + new bootstrap.Collapse(logsCollapse, { toggle: true }); + } }