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;
|
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">✓</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">✗</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