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 <noreply@anthropic.com>
This commit is contained in:
PaulVua
2026-02-10 11:09:25 +01:00
parent 3d61ce22d3
commit 4ed185de0c

View File

@@ -1663,6 +1663,67 @@ async function selfTestSequence() {
let testsFailed = 0; let testsFailed = 0;
try { try {
// Collect system info at the start
document.getElementById('selftest_status').innerHTML = `
<div class="d-flex align-items-center text-primary">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<span>Collecting system information...</span>
</div>`;
// 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) // Step 0: Check WiFi / Network status (informational, no pass/fail)
document.getElementById('selftest_status').innerHTML = ` document.getElementById('selftest_status').innerHTML = `
<div class="d-flex align-items-center text-primary"> <div class="d-flex align-items-center text-primary">
@@ -1978,37 +2039,42 @@ async function selfTestSequence() {
function copySelfTestReport() { function copySelfTestReport() {
// Build formatted report // Build formatted report
let report = `═══════════════════════════════════════════════════════════════ let report = `===============================================================
NEBULEAIR PRO 4G - SELF TEST REPORT NEBULEAIR PRO 4G - SELF TEST REPORT
═══════════════════════════════════════════════════════════════ ===============================================================
📅 Date: ${selfTestReport.timestamp} DEVICE INFORMATION
🔧 Device ID: ${selfTestReport.deviceId} ------------------
📱 Modem Version: ${selfTestReport.modemVersion} 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 // Add test results
const testNames = { const testNames = {
wifi: '📡 WiFi/Network', wifi: 'WiFi/Network',
modem: '📟 Modem Connection', modem: 'Modem Connection',
sim: '💳 SIM Card', sim: 'SIM Card',
signal: '📶 Signal Strength', signal: 'Signal Strength',
network: '🌐 Network Connection' network: 'Network Connection'
}; };
for (const [testId, name] of Object.entries(testNames)) { for (const [testId, name] of Object.entries(testNames)) {
if (selfTestReport.results[testId]) { if (selfTestReport.results[testId]) {
const result = selfTestReport.results[testId]; const result = selfTestReport.results[testId];
const statusIcon = result.status === 'Passed' ? '' : const statusIcon = result.status === 'Passed' ? '[OK]' :
result.status === 'Failed' ? '' : result.status === 'Failed' ? '[FAIL]' :
result.status.includes('Hotspot') || result.status.includes('WiFi') || result.status.includes('Ethernet') ? '' : '⚠️'; result.status.includes('Hotspot') || result.status.includes('WiFi') || result.status.includes('Ethernet') ? '[INFO]' : '[WARN]';
report += `${name} report += `${statusIcon} ${name}
Status: ${statusIcon} ${result.status} Status: ${result.status}
Detail: ${result.detail} Detail: ${result.detail}
`; `;
@@ -2017,55 +2083,103 @@ function copySelfTestReport() {
// Add summary // Add summary
if (selfTestReport.summary) { if (selfTestReport.summary) {
report += `─────────────────────────────────────────────────────────────── report += `===============================================================
SUMMARY SUMMARY
─────────────────────────────────────────────────────────────── ===============================================================
Passed: ${selfTestReport.summary.passed} Passed: ${selfTestReport.summary.passed}
Failed: ${selfTestReport.summary.failed} Failed: ${selfTestReport.summary.failed}
📊 Status: ${selfTestReport.summary.status} Status: ${selfTestReport.summary.status}
`; `;
} }
// Add raw AT responses // Add raw AT responses
report += `─────────────────────────────────────────────────────────────── report += `===============================================================
RAW AT RESPONSES RAW AT RESPONSES
─────────────────────────────────────────────────────────────── ===============================================================
`; `;
for (const [command, response] of Object.entries(selfTestReport.rawResponses)) { for (const [command, response] of Object.entries(selfTestReport.rawResponses)) {
report += `Command: ${command} report += `--- ${command} ---
Response:
${response} ${response}
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
`; `;
} }
// Add full logs // Add full logs
report += `─────────────────────────────────────────────────────────────── report += `===============================================================
DETAILED LOGS DETAILED LOGS
─────────────────────────────────────────────────────────────── ===============================================================
${document.getElementById('selftest_logs').textContent} ${document.getElementById('selftest_logs').textContent}
═══════════════════════════════════════════════════════════════ ===============================================================
END OF REPORT - Generated by NebuleAir Pro 4G END OF REPORT - Generated by NebuleAir Pro 4G
═══════════════════════════════════════════════════════════════ ===============================================================
`; `;
// Copy to clipboard // Copy to clipboard with fallback
navigator.clipboard.writeText(report).then(function() { copyToClipboard(report);
// Show success feedback }
function copyToClipboard(text) {
const copyBtn = document.getElementById('selfTestCopyBtn'); const copyBtn = document.getElementById('selfTestCopyBtn');
const originalHtml = copyBtn.innerHTML; const originalHtml = copyBtn.innerHTML;
copyBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check me-1" viewBox="0 0 16 16"> // Try modern clipboard API first
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/> if (navigator.clipboard && window.isSecureContext) {
</svg> navigator.clipboard.writeText(text).then(function() {
Copied!`; 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 = '<span class="me-1">&#10003;</span> Copied!';
copyBtn.classList.remove('btn-outline-primary'); copyBtn.classList.remove('btn-outline-primary');
copyBtn.classList.add('btn-success'); copyBtn.classList.add('btn-success');
@@ -2074,10 +2188,27 @@ ${document.getElementById('selftest_logs').textContent}
copyBtn.classList.remove('btn-success'); copyBtn.classList.remove('btn-success');
copyBtn.classList.add('btn-outline-primary'); copyBtn.classList.add('btn-outline-primary');
}, 2000); }, 2000);
}).catch(function(err) { }
console.error('Failed to copy:', err);
alert('Failed to copy to clipboard. Please select and copy the logs manually.'); function showCopyError(copyBtn, originalHtml) {
}); copyBtn.innerHTML = '<span class="me-1">&#10007;</span> 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 });
}
} }