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 = `
+
@@ -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 });
+ }
}