Files
3x-ui-pro/assets/diagnostics/index.html
T

365 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Network Diagnostics</title>
<meta name="robots" content="noindex, nofollow">
<style>
:root {
--bg: #0f1117;
--surface: #1a1d27;
--border: #2d3148;
--accent: #4f86f7;
--accent2: #2ecc71;
--warn: #e67e22;
--error: #e74c3c;
--text: #cdd6f4;
--text-dim: #6c7086;
--radius: 8px;
--mono: 'SFMono-Regular', 'Consolas', 'Liberation Mono', monospace;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-height: 100vh;
padding: 24px 16px;
}
.container { max-width: 900px; margin: 0 auto; }
h1 { font-size: 1.6rem; font-weight: 700; margin-bottom: 6px; color: #fff; }
.subtitle { color: var(--text-dim); font-size: 0.9rem; margin-bottom: 32px; }
.section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 24px;
margin-bottom: 24px;
}
.section-title {
font-size: 1rem;
font-weight: 600;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
color: #fff;
}
.section-title::before {
content: '';
display: block;
width: 4px; height: 18px;
background: var(--accent);
border-radius: 2px;
}
/* Speed test embed */
#speedtest-frame {
width: 100%;
height: 520px;
border: none;
border-radius: var(--radius);
background: #000;
}
/* MTR form */
.mtr-form { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-group label { font-size: 0.82rem; color: var(--text-dim); }
.form-group input[type="number"] {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
padding: 8px 12px;
font-size: 0.95rem;
width: 120px;
}
.form-group input:focus { outline: none; border-color: var(--accent); }
.btn {
background: var(--accent);
border: none;
border-radius: 6px;
color: #fff;
cursor: pointer;
font-size: 0.95rem;
font-weight: 600;
padding: 10px 20px;
transition: opacity 0.15s;
}
.btn:hover { opacity: 0.85; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
#mtr-result {
margin-top: 16px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
font-family: var(--mono);
font-size: 0.82rem;
line-height: 1.5;
padding: 14px 16px;
white-space: pre;
overflow-x: auto;
min-height: 48px;
color: var(--text);
display: none;
}
.status-tag {
display: inline-block;
border-radius: 4px;
font-size: 0.78rem;
font-weight: 600;
padding: 2px 8px;
}
.tag-info { background: rgba(79,134,247,0.2); color: var(--accent); }
.tag-ok { background: rgba(46,204,113,0.2); color: var(--accent2); }
.tag-warn { background: rgba(230,126,34,0.2); color: var(--warn); }
.tag-error { background: rgba(231,76,60,0.2); color: var(--error); }
#mtr-status { margin-top: 10px; font-size: 0.85rem; }
#client-ip { font-family: var(--mono); font-weight: 600; color: var(--accent2); }
/* Download test files */
.file-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.file-card {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
padding: 14px 16px;
text-decoration: none;
color: var(--text);
transition: border-color 0.15s;
display: flex;
flex-direction: column;
gap: 4px;
}
.file-card:hover { border-color: var(--accent); }
.file-size { font-size: 1.3rem; font-weight: 700; color: #fff; }
.file-name { font-size: 0.8rem; color: var(--text-dim); font-family: var(--mono); }
/* WinMTR instructions */
.instruction-steps { list-style: none; counter-reset: steps; }
.instruction-steps li {
counter-increment: steps;
padding: 8px 0 8px 40px;
position: relative;
border-bottom: 1px solid var(--border);
font-size: 0.9rem;
}
.instruction-steps li:last-child { border-bottom: none; }
.instruction-steps li::before {
content: counter(steps);
position: absolute;
left: 0;
background: var(--accent);
border-radius: 50%;
width: 24px; height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
color: #fff;
top: 8px;
}
.code-inline {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
font-family: var(--mono);
font-size: 0.85rem;
padding: 2px 6px;
}
#server-ip { font-family: var(--mono); color: var(--accent); }
a.link { color: var(--accent); text-decoration: none; }
a.link:hover { text-decoration: underline; }
.spinner { display: none; }
.spinner.active {
display: inline-block;
width: 14px; height: 14px;
border: 2px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
vertical-align: middle;
margin-left: 8px;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="container">
<h1>Network Diagnostics</h1>
<p class="subtitle">Server diagnostics panel — <span class="status-tag tag-info">Private</span></p>
<!-- ── Speed Test ──────────────────────────────────────────────────── -->
<div class="section">
<div class="section-title">Speed Test</div>
<p style="font-size:0.85rem;color:var(--text-dim);margin-bottom:12px;">
Runs directly between your browser and this server. Served locally — no external connections.
</p>
<iframe id="speedtest-frame"
src="__DIAG_PATH__speedtest/"
allow="fullscreen"
loading="lazy"
title="OpenSpeedTest">
</iframe>
</div>
<!-- ── MTR Test ───────────────────────────────────────────────────── -->
<div class="section">
<div class="section-title">MTR Network Path Test</div>
<p style="font-size:0.85rem;color:var(--text-dim);margin-bottom:16px;">
Runs <code class="code-inline">mtr</code> from this server to your IP address
(<span id="client-ip">detecting…</span>). Shows hop-by-hop latency and packet loss.
</p>
<div class="mtr-form">
<div class="form-group">
<label for="pkt-count">Packet count (1100)</label>
<input type="number" id="pkt-count" value="5" min="1" max="100">
</div>
<button class="btn" id="mtr-btn" onclick="runMtr()">Run MTR</button>
<span class="spinner" id="mtr-spinner"></span>
</div>
<div id="mtr-status"></div>
<pre id="mtr-result"></pre>
</div>
<!-- ── Download Test Files ────────────────────────────────────────── -->
<div class="section">
<div class="section-title">Download Test Files</div>
<p style="font-size:0.85rem;color:var(--text-dim);margin-bottom:16px;">
Static binary files for bandwidth testing. Generated from <code class="code-inline">/dev/zero</code>.
</p>
<div class="file-grid">
<a class="file-card" href="__DIAG_PATH__testfiles/test-15k.bin" download>
<span class="file-size">15 KB</span>
<span class="file-name">test-15k.bin</span>
</a>
<a class="file-card" href="__DIAG_PATH__testfiles/test-17k.bin" download>
<span class="file-size">17 KB</span>
<span class="file-name">test-17k.bin</span>
</a>
<a class="file-card" href="__DIAG_PATH__testfiles/test-100m.bin" download>
<span class="file-size">100 MB</span>
<span class="file-name">test-100m.bin</span>
</a>
<a class="file-card" href="__DIAG_PATH__testfiles/test-1g.bin" download>
<span class="file-size">1 GB</span>
<span class="file-name">test-1g.bin</span>
</a>
</div>
</div>
<!-- ── WinMTR Instructions ────────────────────────────────────────── -->
<div class="section">
<div class="section-title">Run MTR from Windows (WinMTR)</div>
<p style="font-size:0.85rem;color:var(--text-dim);margin-bottom:16px;">
Test the network path from <em>your PC</em> to this server at
<code class="code-inline" id="server-display">__SERVER_DOMAIN__</code>
(IP: <span id="server-ip">__SERVER_IP__</span>).
</p>
<ol class="instruction-steps">
<li>
Download WinMTR from the official site:
<a class="link" href="https://github.com/White-Tiger/WinMTR/releases" target="_blank" rel="noopener noreferrer">
github.com/White-Tiger/WinMTR</a>
— extract the ZIP and run <code class="code-inline">WinMTR.exe</code>.
</li>
<li>
In the <strong>Host</strong> field, enter either the server IP
(<code class="code-inline" id="server-ip-step">__SERVER_IP__</code>)
or the domain
(<code class="code-inline">__SERVER_DOMAIN__</code>).
</li>
<li>
Set <strong>Max hosts</strong> to <code class="code-inline">30</code> and
<strong>Interval</strong> to <code class="code-inline">1000 ms</code>.
</li>
<li>
Click <strong>Start</strong> and let it run for at least <strong>300 packets</strong> (about 5 minutes).
</li>
<li>
Click <strong>Export TEXT</strong> to save the results, then share them with your support team.
</li>
</ol>
</div>
</div>
<script>
// Detect client IP from server response header or API
(async function detectIP() {
try {
const r = await fetch('__DIAG_PATH__api/mtr', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'count=1&detect_only=1'
});
const d = await r.json();
if (d.target) {
document.getElementById('client-ip').textContent = d.target;
}
} catch { /* non-critical */ }
})();
async function runMtr() {
const btn = document.getElementById('mtr-btn');
const spinner = document.getElementById('mtr-spinner');
const resultEl = document.getElementById('mtr-result');
const statusEl = document.getElementById('mtr-status');
// Validate input
const countInput = document.getElementById('pkt-count');
const count = parseInt(countInput.value, 10);
if (isNaN(count) || count < 1 || count > 100) {
statusEl.innerHTML = '<span class="status-tag tag-error">Packet count must be 1100</span>';
return;
}
btn.disabled = true;
spinner.classList.add('active');
resultEl.style.display = 'none';
statusEl.innerHTML = '<span class="status-tag tag-info">Running MTR… this may take up to ' +
(count * 3) + ' seconds</span>';
try {
const resp = await fetch('__DIAG_PATH__api/mtr', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({count})
});
const data = await resp.json();
spinner.classList.remove('active');
btn.disabled = false;
if (resp.status === 429) {
statusEl.innerHTML = '<span class="status-tag tag-warn">Rate limit reached — please wait 60 seconds</span>';
return;
}
if (data.success) {
document.getElementById('client-ip').textContent = data.target || '';
statusEl.innerHTML = '<span class="status-tag tag-ok">MTR complete — ' +
count + ' packets to ' + (data.target || 'your IP') + '</span>';
resultEl.textContent = data.output || '(no output)';
} else {
statusEl.innerHTML = '<span class="status-tag tag-error">Error: ' +
escHtml(data.error || 'unknown') + '</span>';
resultEl.textContent = data.output || '';
}
resultEl.style.display = 'block';
} catch (err) {
spinner.classList.remove('active');
btn.disabled = false;
statusEl.innerHTML = '<span class="status-tag tag-error">Request failed: ' + escHtml(String(err)) + '</span>';
}
}
function escHtml(s) {
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
</script>
</body>
</html>