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;
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)
document.getElementById('selftest_status').innerHTML = `
<div class="d-flex align-items-center text-primary">
@@ -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 = `
<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">
<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"/>
</svg>
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 = '<span class="me-1">&#10003;</span> 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 = '<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 });
}
}