101 lines
5.3 KiB
HTML
101 lines
5.3 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Speed Test</title>
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{font-family:monospace;padding:40px;background:white;color:black}
|
|
h1{margin-bottom:20px}
|
|
.box{border:1px solid black;padding:20px;max-width:500px}
|
|
button{border:1px solid black;background:white;padding:8px 16px;cursor:pointer;font-family:monospace;margin:2px}
|
|
button:hover{background:black;color:white}
|
|
#status{margin-top:10px}
|
|
#progress-wrap{display:none;margin-top:10px}
|
|
#bar{width:100%;height:20px;border:1px solid black}
|
|
#fill{height:100%;background:black;width:0%}
|
|
#text{margin-top:4px}
|
|
.nav{margin-top:40px;font-size:12px}
|
|
a{color:black}
|
|
.section{margin-bottom:10px}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Speed Test</h1>
|
|
<div class="box">
|
|
<div class="section">
|
|
<b>Download:</b><br>
|
|
<button onclick="testDown(10)">10 MB</button>
|
|
<button onclick="testDown(50)">50 MB</button>
|
|
<button onclick="testDown(100)">100 MB</button>
|
|
<button onclick="testDown(250)">250 MB</button>
|
|
</div>
|
|
<div class="section" id="uploadSection" style="display:none">
|
|
<b>Upload:</b><br>
|
|
<button onclick="testUp(10)">10 MB</button>
|
|
<button onclick="testUp(50)">50 MB</button>
|
|
<button onclick="testUp(100)">100 MB</button>
|
|
<button onclick="testUp(250)">250 MB</button>
|
|
</div>
|
|
<div id="status"></div>
|
|
<div id="progress-wrap">
|
|
<div id="bar"><div id="fill"></div></div>
|
|
<div id="text"></div>
|
|
</div>
|
|
</div>
|
|
<div class="nav"><a href="/">Home</a> | <a href="/speedtest">Speed Test</a> | <a id="navAuth" href="/login">Login</a> | <a href="/terms">Terms</a> | <a href="/privacy">Privacy</a></div>
|
|
<script>
|
|
function getToken(){return localStorage.getItem('upload_token')||''}
|
|
function authHeaders(){return{'Authorization':'Bearer '+getToken()}}
|
|
function formatBytes(b){if(b<1024)return b+' B';if(b<1048576)return(b/1024).toFixed(1)+' KB';if(b<1073741824)return(b/1048576).toFixed(1)+' MB';return(b/1073741824).toFixed(2)+' GB'}
|
|
function formatTime(s){if(s<60)return Math.ceil(s)+'s';if(s<3600)return Math.floor(s/60)+'m '+Math.ceil(s%60)+'s';return Math.floor(s/3600)+'h '+Math.floor((s%3600)/60)+'m'}
|
|
const nav=document.getElementById('navAuth');
|
|
if(getToken()){
|
|
nav.href='/dashboard';
|
|
nav.textContent='Dashboard';
|
|
document.getElementById('uploadSection').style.display='block';
|
|
}
|
|
|
|
async function testDown(mb){
|
|
const status=document.getElementById('status');
|
|
const wrap=document.getElementById('progress-wrap');
|
|
const fill=document.getElementById('fill');
|
|
const text=document.getElementById('text');
|
|
const total=mb*1024*1024;
|
|
status.textContent='Getting download URL...';wrap.style.display='block';fill.style.width='0%';text.textContent='';
|
|
const resp=await fetch('/api/speedtest/download-url/'+mb);
|
|
const data=await resp.json();
|
|
status.textContent='Downloading '+mb+' MB...';
|
|
const xhr=new XMLHttpRequest();
|
|
xhr.open('GET',data.download_url);xhr.responseType='blob';
|
|
const start=Date.now();
|
|
xhr.onprogress=function(e){if(!e.lengthComputable)return;const pct=(e.loaded/e.total)*100;fill.style.width=pct+'%';const elapsed=(Date.now()-start)/1000;const speed=e.loaded/elapsed;text.textContent=pct.toFixed(1)+'% | '+formatBytes(e.loaded)+' / '+formatBytes(e.total)+' | '+formatBytes(speed)+'/s'};
|
|
xhr.onload=function(){if(xhr.status>=200&&xhr.status<300){const elapsed=(Date.now()-start)/1000;const speed=total/elapsed;fill.style.width='100%';status.textContent='Download: '+formatBytes(speed)+'/s ('+mb+' MB in '+formatTime(elapsed)+')';text.textContent='100%'}else{status.textContent='Download failed: '+xhr.status+' (upload test data first)'}};
|
|
xhr.onerror=function(){status.textContent='Download failed (upload test data for this size first)'};
|
|
xhr.send();
|
|
}
|
|
|
|
async function testUp(mb){
|
|
const status=document.getElementById('status');
|
|
const wrap=document.getElementById('progress-wrap');
|
|
const fill=document.getElementById('fill');
|
|
const text=document.getElementById('text');
|
|
const total=mb*1024*1024;
|
|
if(!getToken()){status.textContent='No token. Go to Login page first.';return}
|
|
status.textContent='Getting upload URL...';wrap.style.display='block';fill.style.width='0%';text.textContent='';
|
|
const resp=await fetch('/api/speedtest/upload-url/'+mb,{headers:authHeaders()});
|
|
const data=await resp.json();
|
|
if(!resp.ok){status.textContent='Error: '+(data.error||'unauthorized');return}
|
|
status.textContent='Uploading '+mb+' MB...';
|
|
const blob=new Blob([new ArrayBuffer(total)]);
|
|
const xhr=new XMLHttpRequest();
|
|
xhr.open('PUT',data.upload_url);
|
|
const start=Date.now();
|
|
xhr.upload.onprogress=function(e){if(!e.lengthComputable)return;const pct=(e.loaded/e.total)*100;fill.style.width=pct+'%';const elapsed=(Date.now()-start)/1000;const speed=e.loaded/elapsed;text.textContent=pct.toFixed(1)+'% | '+formatBytes(e.loaded)+' / '+formatBytes(e.total)+' | '+formatBytes(speed)+'/s | ETA '+formatTime((e.total-e.loaded)/speed)};
|
|
xhr.onload=function(){if(xhr.status>=200&&xhr.status<300){const elapsed=(Date.now()-start)/1000;const speed=total/elapsed;fill.style.width='100%';status.textContent='Upload: '+formatBytes(speed)+'/s ('+mb+' MB in '+formatTime(elapsed)+')';text.textContent='100%'}else{status.textContent='Upload failed: '+xhr.status}};
|
|
xhr.onerror=function(){status.textContent='Upload failed: network error'};
|
|
xhr.send(blob);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|