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:
217
html/saraR4.html
217
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 = `
|
||||
<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,37 +2039,42 @@ async function selfTestSequence() {
|
||||
|
||||
function copySelfTestReport() {
|
||||
// Build formatted report
|
||||
let 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
|
||||
───────────────────────────────────────────────────────────────
|
||||
===============================================================
|
||||
|
||||
`;
|
||||
|
||||
// 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}
|
||||
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,55 +2083,103 @@ function copySelfTestReport() {
|
||||
|
||||
// Add summary
|
||||
if (selfTestReport.summary) {
|
||||
report += `───────────────────────────────────────────────────────────────
|
||||
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 += `───────────────────────────────────────────────────────────────
|
||||
report += `===============================================================
|
||||
RAW AT RESPONSES
|
||||
───────────────────────────────────────────────────────────────
|
||||
===============================================================
|
||||
|
||||
`;
|
||||
|
||||
for (const [command, response] of Object.entries(selfTestReport.rawResponses)) {
|
||||
report += `Command: ${command}
|
||||
Response:
|
||||
report += `--- ${command} ---
|
||||
${response}
|
||||
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
// Add full logs
|
||||
report += `───────────────────────────────────────────────────────────────
|
||||
report += `===============================================================
|
||||
DETAILED LOGS
|
||||
───────────────────────────────────────────────────────────────
|
||||
===============================================================
|
||||
|
||||
${document.getElementById('selftest_logs').textContent}
|
||||
|
||||
═══════════════════════════════════════════════════════════════
|
||||
===============================================================
|
||||
END OF REPORT - Generated by NebuleAir Pro 4G
|
||||
═══════════════════════════════════════════════════════════════
|
||||
===============================================================
|
||||
`;
|
||||
|
||||
// Copy to clipboard
|
||||
navigator.clipboard.writeText(report).then(function() {
|
||||
// Show success feedback
|
||||
// Copy to clipboard with fallback
|
||||
copyToClipboard(report);
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
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!`;
|
||||
|
||||
// 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">✓</span> Copied!';
|
||||
copyBtn.classList.remove('btn-outline-primary');
|
||||
copyBtn.classList.add('btn-success');
|
||||
|
||||
@@ -2074,10 +2188,27 @@ ${document.getElementById('selftest_logs').textContent}
|
||||
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 showCopyError(copyBtn, originalHtml) {
|
||||
copyBtn.innerHTML = '<span class="me-1">✗</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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user