diff --git a/assets/diagnostics/index.html b/assets/diagnostics/index.html index d135002..65d8d54 100644 --- a/assets/diagnostics/index.html +++ b/assets/diagnostics/index.html @@ -54,14 +54,24 @@ border-radius: 2px; } - /* Speed test embed */ - #speedtest-frame { - width: 100%; - height: 520px; - border: none; - border-radius: var(--radius); - background: #000; + /* Speed test */ + .speed-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-bottom: 20px; } + .speed-card { + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 24px 16px; + text-align: center; + } + .speed-card-label { font-size: 0.85rem; color: var(--text-dim); margin-bottom: 10px; } + .speed-card-value { font-size: 2.8rem; font-weight: 700; color: #fff; line-height: 1; font-variant-numeric: tabular-nums; } + .speed-card-unit { font-size: 0.8rem; color: var(--text-dim); margin-top: 4px; } + .speed-card-note { font-size: 0.75rem; color: var(--text-dim); margin-top: 8px; min-height: 1em; } /* MTR form */ .mtr-form { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } @@ -195,15 +205,26 @@
Speed Test
-

- Runs directly between your browser and this server. Served locally — no external connections. +

+ Runs directly between your browser and this server — no external connections. + Download: 100 MB test file. Upload: 25 MB payload.

- +
+
+
↓ Download
+
+
Mbps
+
+
+
+
↑ Upload
+
+
Mbps
+
+
+
+ +
@@ -359,6 +380,69 @@ async function runMtr() { function escHtml(s) { return s.replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } + +// ── Speed Test ────────────────────────────────────────────────────────────── +async function runSpeedTest() { + const btn = document.getElementById('speed-btn'); + const status = document.getElementById('speed-status'); + btn.disabled = true; + + ['dl-speed','ul-speed'].forEach(id => { document.getElementById(id).textContent = '—'; }); + ['dl-note','ul-note'].forEach(id => { document.getElementById(id).textContent = ''; }); + + // Download + try { + status.innerHTML = 'Testing download…'; + const mbps = await measureDownload(); + document.getElementById('dl-speed').textContent = mbps.toFixed(1); + document.getElementById('dl-note').textContent = '100 MB file'; + } catch(e) { + document.getElementById('dl-speed').textContent = 'ERR'; + } + + // Upload + try { + status.innerHTML = 'Testing upload…'; + const mbps = await measureUpload(); + document.getElementById('ul-speed').textContent = mbps.toFixed(1); + document.getElementById('ul-note').textContent = '25 MB payload'; + } catch(e) { + document.getElementById('ul-speed').textContent = 'ERR'; + } + + status.innerHTML = 'Done'; + btn.disabled = false; +} + +async function measureDownload() { + const url = '__DIAG_PATH__testfiles/test-100m.bin?t=' + Date.now(); + const start = performance.now(); + const resp = await fetch(url, { cache: 'no-store' }); + const reader = resp.body.getReader(); + let bytes = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + bytes += value.length; + const secs = (performance.now() - start) / 1000; + if (secs > 0.3) + document.getElementById('dl-speed').textContent = ((bytes * 8) / secs / 1e6).toFixed(1); + } + return (bytes * 8) / ((performance.now() - start) / 1000) / 1e6; +} + +async function measureUpload() { + const size = 25 * 1024 * 1024; + const data = new Uint8Array(size); + const start = performance.now(); + const resp = await fetch('__DIAG_PATH__api/upload', { + method: 'POST', + body: data, + headers: { 'Content-Type': 'application/octet-stream' } + }); + await resp.json(); + return (size * 8) / ((performance.now() - start) / 1000) / 1e6; +} diff --git a/assets/diagnostics/mtr-backend.py b/assets/diagnostics/mtr-backend.py index 019117c..fbab53c 100644 --- a/assets/diagnostics/mtr-backend.py +++ b/assets/diagnostics/mtr-backend.py @@ -187,6 +187,22 @@ class Handler(BaseHTTPRequestHandler): def do_POST(self): parsed = urlparse(self.path) + + # ── Upload speed test receiver ───────────────────────────────────── + # Reads the entire body before responding so the client timer is accurate + if parsed.path == "/api/upload" or parsed.path.endswith("/api/upload"): + content_length = int(self.headers.get("Content-Length", "0")) + to_read = min(content_length, 128 * 1024 * 1024) # cap at 128 MB + received = 0 + buf = 65536 + while received < to_read: + chunk = self.rfile.read(min(buf, to_read - received)) + if not chunk: + break + received += len(chunk) + self._send_json(200, {"received": received, "ok": True}) + return + if not (parsed.path in ("/mtr", "/api/mtr") or parsed.path.endswith("/api/mtr")): self._send_json(404, {"error": "not found"}) return diff --git a/x-ui-latest.sh b/x-ui-latest.sh index ebb7c66..6b3eb8b 100644 --- a/x-ui-latest.sh +++ b/x-ui-latest.sh @@ -141,7 +141,7 @@ uninstall_xui() { $Pak -y purge nginx nginx-common nginx-core nginx-full python3-certbot-nginx $Pak -y autoremove $Pak -y autoclean - rm -rf /var/www/html/ /var/www/diagnostics/ /var/www/openspeedtest/ /etc/nginx/ /usr/share/nginx/ + rm -rf /var/www/html/ /var/www/diagnostics/ /etc/nginx/ /usr/share/nginx/ systemctl stop mtr-backend 2>/dev/null || true systemctl disable mtr-backend 2>/dev/null || true rm -f /etc/systemd/system/mtr-backend.service @@ -470,15 +470,19 @@ server { proxy_send_timeout 120s; } - # ── OpenSpeedTest (served locally, no external connections) ───────────── - location ^~ ${diag_path}speedtest/ { - limit_req zone=diag_page burst=30 nodelay; - alias /var/www/openspeedtest/; - index index.html; - try_files \$uri \$uri/ =404; - client_max_body_size 35m; - add_header X-Frame-Options "SAMEORIGIN" always; - access_log off; + # ── Speed test upload receiver ─────────────────────────────────────────── + # proxy_request_buffering off = nginx streams body to backend in real time; + # backend responds only after reading all bytes → accurate timing on client + location ^~ ${diag_path}api/upload { + limit_req zone=diag_page burst=5 nodelay; + proxy_pass http://127.0.0.1:${mtr_backend_port}/api/upload; + proxy_http_version 1.1; + proxy_set_header X-Real-IP \$remote_addr; + proxy_request_buffering off; + client_max_body_size 128m; + proxy_read_timeout 120s; + proxy_send_timeout 120s; + add_header Cache-Control "no-store" always; } # ── Download test files ────────────────────────────────────────────────── @@ -872,18 +876,6 @@ install_fake_site() { install_diagnostics() { local diag_webroot="/var/www/diagnostics" local backend_script="/usr/local/lib/3x-ui-pro/mtr-backend.py" - local openspeedtest_webroot="/var/www/openspeedtest" - - # OpenSpeedTest static files - if [[ ! -f "${openspeedtest_webroot}/index.html" ]]; then - mkdir -p "${openspeedtest_webroot}" - curl -fsSL --retry 3 \ - "https://github.com/openspeedtest/Speed-Test/archive/refs/heads/main.tar.gz" \ - -o /tmp/openspeedtest.tar.gz - tar -xzf /tmp/openspeedtest.tar.gz -C "${openspeedtest_webroot}" --strip-components=1 - rm -f /tmp/openspeedtest.tar.gz - chown -R www-data:www-data "${openspeedtest_webroot}" 2>/dev/null || true - fi # Diagnostics HTML page mkdir -p "${diag_webroot}"