first commit
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user