triflix commited on
Commit
38dfb36
·
verified ·
1 Parent(s): ab5cf5c

Upload 6 files

Browse files
main.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, BackgroundTasks
2
+ from fastapi.templating import Jinja2Templates
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import HTMLResponse, JSONResponse
5
+ import speedtest
6
+ import time
7
+ import uuid
8
+ import os
9
+ from pydantic import BaseModel
10
+
11
+ app = FastAPI(title="Internet Speed Test")
12
+
13
+ # Create templates directory if it doesn't exist
14
+ os.makedirs("templates", exist_ok=True)
15
+
16
+ # Mount static files
17
+ app.mount("/static", StaticFiles(directory="static"), name="static")
18
+
19
+ # Templates
20
+ templates = Jinja2Templates(directory="templates")
21
+
22
+ # Store test results
23
+ test_results = {}
24
+
25
+ class SpeedTestResult(BaseModel):
26
+ download_mbps: float
27
+ upload_mbps: float
28
+ ping: float
29
+ download_MBps: float
30
+ upload_MBps: float
31
+ status: str
32
+ progress: int = 0
33
+
34
+ @app.get("/", response_class=HTMLResponse)
35
+ async def home(request: Request):
36
+ return templates.TemplateResponse("index.html", {"request": request})
37
+
38
+ def run_speed_test(test_id: str):
39
+ try:
40
+ # Update progress
41
+ test_results[test_id].progress = 10
42
+ test_results[test_id].status = "Initializing..."
43
+
44
+ # Initialize speedtest
45
+ st = speedtest.Speedtest()
46
+
47
+ # Get best server
48
+ test_results[test_id].status = "Finding best server..."
49
+ test_results[test_id].progress = 20
50
+ st.get_best_server()
51
+
52
+ # Measure download speed
53
+ test_results[test_id].status = "Testing download speed..."
54
+ test_results[test_id].progress = 30
55
+ start_time = time.time()
56
+ download_speed = st.download()
57
+ end_time = time.time()
58
+
59
+ # Calculate download speed in MB/s
60
+ download_speed_MBps = (download_speed / 8) / 1_000_000
61
+ test_results[test_id].download_MBps = round(download_speed_MBps, 2)
62
+ test_results[test_id].download_mbps = round(download_speed / 1_000_000, 2)
63
+ test_results[test_id].progress = 60
64
+
65
+ # Measure upload speed
66
+ test_results[test_id].status = "Testing upload speed..."
67
+ start_time = time.time()
68
+ upload_speed = st.upload()
69
+ end_time = time.time()
70
+
71
+ # Calculate upload speed in MB/s
72
+ upload_speed_MBps = (upload_speed / 8) / 1_000_000
73
+ test_results[test_id].upload_MBps = round(upload_speed_MBps, 2)
74
+ test_results[test_id].upload_mbps = round(upload_speed / 1_000_000, 2)
75
+ test_results[test_id].progress = 90
76
+
77
+ # Get ping
78
+ ping = st.results.ping
79
+ test_results[test_id].ping = round(ping, 2)
80
+
81
+ # Update status
82
+ test_results[test_id].status = "Complete"
83
+ test_results[test_id].progress = 100
84
+
85
+ except Exception as e:
86
+ test_results[test_id].status = f"Error: {str(e)}"
87
+ test_results[test_id].progress = -1
88
+
89
+ @app.post("/start-test")
90
+ async def start_test(background_tasks: BackgroundTasks):
91
+ test_id = str(uuid.uuid4())
92
+ test_results[test_id] = SpeedTestResult(
93
+ download_mbps=0,
94
+ upload_mbps=0,
95
+ ping=0,
96
+ download_MBps=0,
97
+ upload_MBps=0,
98
+ status="Starting...",
99
+ progress=0
100
+ )
101
+
102
+ background_tasks.add_task(run_speed_test, test_id)
103
+ return {"test_id": test_id}
104
+
105
+ @app.get("/test-status/{test_id}")
106
+ async def test_status(test_id: str):
107
+ if test_id not in test_results:
108
+ return JSONResponse(status_code=404, content={"error": "Test not found"})
109
+
110
+ return test_results[test_id]
111
+
112
+ if __name__ == "__main__":
113
+ import uvicorn
114
+ uvicorn.run(app, host="0.0.0.0", port=8000)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ jinja2
4
+ speedtest-cli
static/css/style.css ADDED
File without changes
templates/index.html ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Speed Test - Glassmorphism UI</title>
7
+ <!-- Optional: Font Awesome for icons -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <!-- Google Font for a clean, modern feel -->
10
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap">
11
+
12
+ <style>
13
+ /* -----------------------------
14
+ BASIC RESET & BODY STYLING
15
+ ------------------------------*/
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ }
21
+ body {
22
+ font-family: 'Inter', sans-serif;
23
+ background: linear-gradient(to right, #0a0b0d, #17181b);
24
+ color: #fff;
25
+ min-height: 100vh;
26
+ display: flex;
27
+ justify-content: center;
28
+ align-items: flex-start;
29
+ padding: 2rem;
30
+ }
31
+
32
+ /* -----------------------------
33
+ DASHBOARD GRID
34
+ ------------------------------*/
35
+ .dashboard {
36
+ display: grid;
37
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
38
+ gap: 1.5rem;
39
+ max-width: 1200px;
40
+ width: 100%;
41
+ }
42
+
43
+ /* -----------------------------
44
+ GLASSMORPHISM CARD BASE
45
+ ------------------------------*/
46
+ .card {
47
+ position: relative;
48
+ background: rgba(255, 255, 255, 0.05);
49
+ border-radius: 1rem;
50
+ overflow: hidden;
51
+ backdrop-filter: blur(15px);
52
+ -webkit-backdrop-filter: blur(15px);
53
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
54
+ display: flex;
55
+ flex-direction: column;
56
+ padding: 1.5rem;
57
+ }
58
+ .card::before {
59
+ content: "";
60
+ position: absolute;
61
+ inset: 0;
62
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.02), rgba(255, 153, 0, 0.08));
63
+ pointer-events: none;
64
+ mix-blend-mode: overlay;
65
+ }
66
+ .card h2, .card h3, .card h4, .card h5, .card p {
67
+ position: relative;
68
+ margin-bottom: 0.4rem;
69
+ }
70
+ .card h2, .card h3 {
71
+ font-weight: 500;
72
+ }
73
+
74
+ /* -----------------------------
75
+ 1) DOWNLOAD SPEED CARD
76
+ ------------------------------*/
77
+ .download-card .metric-value {
78
+ font-size: 2rem;
79
+ font-weight: 700;
80
+ margin-bottom: 0.2rem;
81
+ }
82
+ .download-card .sub-info {
83
+ font-size: 0.85rem;
84
+ opacity: 0.7;
85
+ margin-bottom: 0.6rem;
86
+ }
87
+ .download-card .download-icon {
88
+ font-size: 2rem;
89
+ color: #ff9900;
90
+ margin-bottom: 1rem;
91
+ }
92
+
93
+ /* -----------------------------
94
+ 2) UPLOAD SPEED CARD
95
+ ------------------------------*/
96
+ .upload-card .metric-value {
97
+ font-size: 2rem;
98
+ font-weight: 700;
99
+ margin-bottom: 0.2rem;
100
+ }
101
+ .upload-card .sub-info {
102
+ font-size: 0.85rem;
103
+ opacity: 0.7;
104
+ margin-bottom: 0.6rem;
105
+ }
106
+ .upload-card .upload-icon {
107
+ font-size: 2rem;
108
+ color: #ff9900;
109
+ margin-bottom: 1rem;
110
+ }
111
+
112
+ /* -----------------------------
113
+ 3) PING CARD
114
+ ------------------------------*/
115
+ .ping-card .metric-value {
116
+ font-size: 2rem;
117
+ font-weight: 700;
118
+ margin-bottom: 0.2rem;
119
+ }
120
+ .ping-card .sub-info {
121
+ font-size: 0.85rem;
122
+ opacity: 0.7;
123
+ margin-bottom: 0.6rem;
124
+ }
125
+ .ping-card .ping-icon {
126
+ font-size: 2rem;
127
+ color: #ff9900;
128
+ margin-bottom: 1rem;
129
+ }
130
+
131
+ /* -----------------------------
132
+ 4) START TEST CARD (Large, center focus)
133
+ ------------------------------*/
134
+ .start-test-card {
135
+ background: url('https://via.placeholder.com/300x400?text=SpeedTest+Blur') center/cover no-repeat;
136
+ background-blend-mode: overlay;
137
+ background-color: rgba(0,0,0,0.5);
138
+ display: flex;
139
+ flex-direction: column;
140
+ justify-content: flex-end;
141
+ color: #fff;
142
+ text-align: center;
143
+ padding: 2rem;
144
+ }
145
+ .start-test-card h3 {
146
+ font-size: 1.3rem;
147
+ margin-bottom: 0.4rem;
148
+ }
149
+ .start-test-card p {
150
+ font-size: 0.9rem;
151
+ opacity: 0.8;
152
+ margin-bottom: 1.2rem;
153
+ }
154
+ .start-btn {
155
+ display: inline-block;
156
+ background-color: rgba(255,255,255,0.1);
157
+ border: 1px solid rgba(255, 255, 255, 0.2);
158
+ padding: 0.8rem 1.2rem;
159
+ border-radius: 0.5rem;
160
+ color: #fff;
161
+ font-size: 1rem;
162
+ cursor: pointer;
163
+ transition: background 0.3s;
164
+ }
165
+ .start-btn:hover {
166
+ background-color: rgba(255, 153, 0, 0.2);
167
+ }
168
+
169
+ /* -----------------------------
170
+ 5) CLOCK CARD / STATUS CARD
171
+ ------------------------------*/
172
+ .clock-card {
173
+ display: flex;
174
+ flex-direction: column;
175
+ align-items: center;
176
+ text-align: center;
177
+ }
178
+ .clock-card h4 {
179
+ margin-top: 1rem;
180
+ font-size: 1.2rem;
181
+ margin-bottom: 0.5rem;
182
+ }
183
+ .clock-face {
184
+ margin-top: 1rem;
185
+ width: 80px;
186
+ height: 80px;
187
+ border: 4px solid rgba(255,255,255,0.3);
188
+ border-radius: 50%;
189
+ position: relative;
190
+ }
191
+ .clock-face::before {
192
+ content: "";
193
+ position: absolute;
194
+ top: 50%;
195
+ left: 50%;
196
+ width: 4px;
197
+ height: 20px;
198
+ background: #ff9900;
199
+ transform: translate(-50%, -90%);
200
+ border-radius: 2px;
201
+ }
202
+
203
+ /* -----------------------------
204
+ 6) PROGRESS CARD
205
+ ------------------------------*/
206
+ .progress-card .progress-icon {
207
+ font-size: 2rem;
208
+ color: #ff9900;
209
+ margin-bottom: 1rem;
210
+ }
211
+ .progress-card .progress-value {
212
+ font-size: 1.6rem;
213
+ font-weight: 600;
214
+ margin-bottom: 0.2rem;
215
+ }
216
+ .progress-card .small-text {
217
+ font-size: 0.85rem;
218
+ opacity: 0.7;
219
+ }
220
+
221
+ /* -----------------------------
222
+ 7) RESULTS / INFO CARD
223
+ ------------------------------*/
224
+ .results-card {
225
+ text-align: center;
226
+ }
227
+ .results-card h4 {
228
+ font-size: 1.2rem;
229
+ margin-bottom: 0.8rem;
230
+ }
231
+ .results-card .summary {
232
+ font-size: 0.9rem;
233
+ opacity: 0.8;
234
+ }
235
+ </style>
236
+ </head>
237
+ <body>
238
+
239
+ <div class="dashboard">
240
+ <!-- 1) DOWNLOAD SPEED CARD -->
241
+ <div class="card download-card">
242
+ <i class="fas fa-download download-icon"></i>
243
+ <h3>Download Speed</h3>
244
+ <div class="metric-value" id="downloadValue">--</div>
245
+ <p class="sub-info">Mbps</p>
246
+ <p class="sub-info">(<span id="downloadMBps">--</span> MB/s)</p>
247
+ </div>
248
+
249
+ <!-- 2) UPLOAD SPEED CARD -->
250
+ <div class="card upload-card">
251
+ <i class="fas fa-upload upload-icon"></i>
252
+ <h3>Upload Speed</h3>
253
+ <div class="metric-value" id="uploadValue">--</div>
254
+ <p class="sub-info">Mbps</p>
255
+ <p class="sub-info">(<span id="uploadMBps">--</span> MB/s)</p>
256
+ </div>
257
+
258
+ <!-- 3) PING CARD -->
259
+ <div class="card ping-card">
260
+ <i class="fas fa-stopwatch ping-icon"></i>
261
+ <h3>Ping</h3>
262
+ <div class="metric-value" id="pingValue">--</div>
263
+ <p class="sub-info">ms</p>
264
+ <p class="sub-info">Rating: <span id="pingRating">--</span></p>
265
+ </div>
266
+
267
+ <!-- 4) START TEST CARD -->
268
+ <div class="card start-test-card">
269
+ <h3>Speed Test Ready</h3>
270
+ <p>Tap below to begin measuring your connection.</p>
271
+ <button class="start-btn" id="startTest">Start Test</button>
272
+ </div>
273
+
274
+ <!-- 5) CLOCK CARD / STATUS CARD -->
275
+ <div class="card clock-card">
276
+ <h4>Test Status</h4>
277
+ <div class="clock-face"></div>
278
+ <p id="statusText" style="margin-top:1rem; opacity:0.8;">Not started</p>
279
+ </div>
280
+
281
+ <!-- 6) PROGRESS CARD -->
282
+ <div class="card progress-card">
283
+ <i class="fas fa-spinner progress-icon"></i>
284
+ <h4>Progress</h4>
285
+ <div class="progress-value" id="progressPercent">0%</div>
286
+ <p class="small-text">Current Step</p>
287
+ </div>
288
+
289
+ <!-- 7) RESULTS CARD -->
290
+ <div class="card results-card">
291
+ <h4>Test Summary</h4>
292
+ <p class="summary" id="resultSummary">No data yet.</p>
293
+ </div>
294
+ </div>
295
+
296
+ <script>
297
+ const startBtn = document.getElementById('startTest');
298
+ const statusText = document.getElementById('statusText');
299
+ const progressPercent = document.getElementById('progressPercent');
300
+ const downloadValue = document.getElementById('downloadValue');
301
+ const uploadValue = document.getElementById('uploadValue');
302
+ const pingValue = document.getElementById('pingValue');
303
+ const downloadMBps = document.getElementById('downloadMBps');
304
+ const pingRating = document.getElementById('pingRating');
305
+ const resultSummary = document.getElementById('resultSummary');
306
+
307
+ let testId = null;
308
+ let pollInterval = null;
309
+
310
+ startBtn.addEventListener('click', async () => {
311
+ // Reset UI
312
+ statusText.textContent = "Starting test...";
313
+ progressPercent.textContent = "0%";
314
+ downloadValue.textContent = "--";
315
+ uploadValue.textContent = "--";
316
+ pingValue.textContent = "--";
317
+ pingRating.textContent = "--";
318
+ downloadMBps.textContent = "--";
319
+ resultSummary.textContent = "Measuring your connection...";
320
+
321
+ // Start test by calling the backend
322
+ try {
323
+ const res = await fetch('/start-test', { method: 'POST' });
324
+ const data = await res.json();
325
+ testId = data.test_id;
326
+ // Begin polling for test status every 1 second
327
+ pollInterval = setInterval(fetchTestStatus, 1000);
328
+ } catch (error) {
329
+ statusText.textContent = "Error starting test.";
330
+ console.error("Error:", error);
331
+ }
332
+ });
333
+
334
+ async function fetchTestStatus() {
335
+ if (!testId) return;
336
+ try {
337
+ const res = await fetch(`/test-status/${testId}`);
338
+ if (res.status !== 200) {
339
+ statusText.textContent = "Test not found.";
340
+ clearInterval(pollInterval);
341
+ return;
342
+ }
343
+ const result = await res.json();
344
+
345
+ // Update UI based on backend response
346
+ statusText.textContent = result.status;
347
+ progressPercent.textContent = result.progress + "%";
348
+ if (result.download_mbps) {
349
+ downloadValue.textContent = result.download_mbps;
350
+ downloadMBps.textContent = result.download_MBps;
351
+ }
352
+ if (result.upload_mbps) {
353
+ uploadValue.textContent = result.upload_mbps;
354
+ document.getElementById('uploadMBps').textContent = result.upload_MBps;
355
+ }
356
+ if (result.ping) {
357
+ pingValue.textContent = result.ping;
358
+ pingRating.textContent = result.ping <= 20 ? "Excellent" :
359
+ result.ping <= 50 ? "Good" :
360
+ result.ping <= 100 ? "Average" : "Poor";
361
+ }
362
+ if (result.progress === 100 || result.progress < 0) {
363
+ // Test complete or encountered error
364
+ clearInterval(pollInterval);
365
+ resultSummary.textContent = `Download: ${result.download_mbps} Mbps, Upload: ${result.upload_mbps} Mbps, Ping: ${result.ping} ms`;
366
+ }
367
+ } catch (error) {
368
+ console.error("Error fetching test status:", error);
369
+ }
370
+ }
371
+ </script>
372
+ </body>
373
+ </html>
templates/index1.html ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Internet Speed Test</title>
7
+ <!-- Bootstrap CSS -->
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
9
+ <!-- Font Awesome -->
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+ <style>
12
+ body {
13
+ background-color: #f8f9fa;
14
+ }
15
+ .speed-card {
16
+ transition: all 0.3s ease;
17
+ }
18
+ .speed-card:hover {
19
+ transform: translateY(-5px);
20
+ box-shadow: 0 10px 20px rgba(0,0,0,0.1);
21
+ }
22
+ .progress {
23
+ height: 10px;
24
+ }
25
+ .result-section {
26
+ display: none;
27
+ }
28
+ .gauge-container {
29
+ width: 200px;
30
+ height: 200px;
31
+ margin: 0 auto;
32
+ position: relative;
33
+ }
34
+ .gauge {
35
+ width: 100%;
36
+ height: 100%;
37
+ position: relative;
38
+ }
39
+ .gauge-value {
40
+ position: absolute;
41
+ top: 50%;
42
+ left: 50%;
43
+ transform: translate(-50%, -50%);
44
+ font-size: 24px;
45
+ font-weight: bold;
46
+ }
47
+ .loading-spinner {
48
+ display: none;
49
+ }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div class="container py-5">
54
+ <div class="row justify-content-center">
55
+ <div class="col-md-10">
56
+ <div class="card shadow-lg">
57
+ <div class="card-header bg-primary text-white">
58
+ <h2 class="text-center mb-0">
59
+ <i class="fas fa-tachometer-alt me-2"></i>Internet Speed Test
60
+ </h2>
61
+ </div>
62
+ <div class="card-body">
63
+ <div class="text-center mb-4">
64
+ <p class="lead">Test your internet connection speed with our fast and reliable tool</p>
65
+ <button id="startTest" class="btn btn-primary btn-lg px-5">
66
+ <i class="fas fa-play me-2"></i>Start Test
67
+ </button>
68
+ <div class="loading-spinner mt-4" id="loadingSpinner">
69
+ <div class="progress mb-3">
70
+ <div class="progress-bar progress-bar-striped progress-bar-animated"
71
+ id="progressBar" role="progressbar" style="width: 0%"></div>
72
+ </div>
73
+ <p id="statusText" class="text-muted">Initializing...</p>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="result-section" id="resultSection">
78
+ <div class="row">
79
+ <div class="col-md-4">
80
+ <div class="card speed-card text-center mb-3">
81
+ <div class="card-body">
82
+ <h5 class="card-title">
83
+ <i class="fas fa-download text-primary me-2"></i>Download
84
+ </h5>
85
+ <div class="gauge-container">
86
+ <div class="gauge">
87
+ <canvas id="downloadGauge"></canvas>
88
+ <div class="gauge-value">
89
+ <span id="downloadValue">0</span> Mbps
90
+ </div>
91
+ </div>
92
+ </div>
93
+ <p class="text-muted mt-2">
94
+ <span id="downloadMBps">0</span> MB/s
95
+ </p>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ <div class="col-md-4">
100
+ <div class="card speed-card text-center mb-3">
101
+ <div class="card-body">
102
+ <h5 class="card-title">
103
+ <i class="fas fa-upload text-success me-2"></i>Upload
104
+ </h5>
105
+ <div class="gauge-container">
106
+ <div class="gauge">
107
+ <canvas id="uploadGauge"></canvas>
108
+ <div class="gauge-value">
109
+ <span id="uploadValue">0</span> Mbps
110
+ </div>
111
+ </div>
112
+ </div>
113
+ <p class="text-muted mt-2">
114
+ <span id="uploadMBps">0</span> MB/s
115
+ </p>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ <div class="col-md-4">
120
+ <div class="card speed-card text-center mb-3">
121
+ <div class="card-body">
122
+ <h5 class="card-title">
123
+ <i class="fas fa-stopwatch text-warning me-2"></i>Ping
124
+ </h5>
125
+ <div class="gauge-container">
126
+ <div class="gauge">
127
+ <canvas id="pingGauge"></canvas>
128
+ <div class="gauge-value">
129
+ <span id="pingValue">0</span> ms
130
+ </div>
131
+ </div>
132
+ </div>
133
+ <p class="text-muted mt-2">
134
+ <span id="pingRating">-</span>
135
+ </p>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+
141
+ <div class="text-center mt-4">
142
+ <button id="retestButton" class="btn btn-outline-primary">
143
+ <i class="fas fa-redo me-2"></i>Test Again
144
+ </button>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </div>
152
+
153
+ <!-- Bootstrap JS -->
154
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
155
+ <!-- Chart.js for gauges -->
156
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
157
+
158
+ <script>
159
+ document.addEventListener('DOMContentLoaded', function() {
160
+ const startButton = document.getElementById('startTest');
161
+ const retestButton = document.getElementById('retestButton');
162
+ const loadingSpinner = document.getElementById('loadingSpinner');
163
+ const resultSection = document.getElementById('resultSection');
164
+ const progressBar = document.getElementById('progressBar');
165
+ const statusText = document.getElementById('statusText');
166
+
167
+ let downloadGauge, uploadGauge, pingGauge;
168
+ let currentTestId = null;
169
+ let pollingInterval = null;
170
+
171
+ // Initialize gauges
172
+ function initGauges() {
173
+ // Download gauge
174
+ downloadGauge = new Chart(document.getElementById('downloadGauge'), {
175
+ type: 'doughnut',
176
+ data: {
177
+ datasets: [{
178
+ data: [0, 100],
179
+ backgroundColor: ['#0d6efd', '#e9ecef']
180
+ }]
181
+ },
182
+ options: {
183
+ cutout: '80%',
184
+ responsive: true,
185
+ maintainAspectRatio: true,
186
+ animation: {
187
+ duration: 1000
188
+ },
189
+ plugins: {
190
+ tooltip: {
191
+ enabled: false
192
+ }
193
+ }
194
+ }
195
+ });
196
+
197
+ // Upload gauge
198
+ uploadGauge = new Chart(document.getElementById('uploadGauge'), {
199
+ type: 'doughnut',
200
+ data: {
201
+ datasets: [{
202
+ data: [0, 100],
203
+ backgroundColor: ['#198754', '#e9ecef']
204
+ }]
205
+ },
206
+ options: {
207
+ cutout: '80%',
208
+ responsive: true,
209
+ maintainAspectRatio: true,
210
+ animation: {
211
+ duration: 1000
212
+ },
213
+ plugins: {
214
+ tooltip: {
215
+ enabled: false
216
+ }
217
+ }
218
+ }
219
+ });
220
+
221
+ // Ping gauge
222
+ pingGauge = new Chart(document.getElementById('pingGauge'), {
223
+ type: 'doughnut',
224
+ data: {
225
+ datasets: [{
226
+ data: [0, 100],
227
+ backgroundColor: ['#ffc107', '#e9ecef']
228
+ }]
229
+ },
230
+ options: {
231
+ cutout: '80%',
232
+ responsive: true,
233
+ maintainAspectRatio: true,
234
+ animation: {
235
+ duration: 1000
236
+ },
237
+ plugins: {
238
+ tooltip: {
239
+ enabled: false
240
+ }
241
+ }
242
+ }
243
+ });
244
+ }
245
+
246
+ // Start the speed test
247
+ async function startTest() {
248
+ startButton.disabled = true;
249
+ loadingSpinner.style.display = 'block';
250
+ resultSection.style.display = 'none';
251
+ progressBar.style.width = '0%';
252
+ statusText.textContent = 'Initializing...';
253
+
254
+ try {
255
+ const response = await fetch('/start-test', {
256
+ method: 'POST'
257
+ });
258
+ const data = await response.json();
259
+ currentTestId = data.test_id;
260
+
261
+ // Start polling for updates
262
+ pollingInterval = setInterval(pollTestStatus, 1000);
263
+ } catch (error) {
264
+ console.error('Error starting test:', error);
265
+ statusText.textContent = 'Error starting test. Please try again.';
266
+ startButton.disabled = false;
267
+ }
268
+ }
269
+
270
+ // Poll for test status
271
+ async function pollTestStatus() {
272
+ try {
273
+ const response = await fetch(`/test-status/${currentTestId}`);
274
+ const data = await response.json();
275
+
276
+ // Update progress
277
+ progressBar.style.width = `${data.progress}%`;
278
+ statusText.textContent = data.status;
279
+
280
+ // Check if test is complete
281
+ if (data.progress === 100) {
282
+ clearInterval(pollingInterval);
283
+ displayResults(data);
284
+ } else if (data.progress === -1) {
285
+ // Error occurred
286
+ clearInterval(pollingInterval);
287
+ statusText.textContent = data.status;
288
+ startButton.disabled = false;
289
+ }
290
+ } catch (error) {
291
+ console.error('Error polling test status:', error);
292
+ clearInterval(pollingInterval);
293
+ statusText.textContent = 'Error getting test results. Please try again.';
294
+ startButton.disabled = false;
295
+ }
296
+ }
297
+
298
+ // Display test results
299
+ function displayResults(data) {
300
+ loadingSpinner.style.display = 'none';
301
+ resultSection.style.display = 'block';
302
+ startButton.disabled = false;
303
+
304
+ // Update download values
305
+ document.getElementById('downloadValue').textContent = data.download_mbps;
306
+ document.getElementById('downloadMBps').textContent = data.download_MBps;
307
+
308
+ // Update upload values
309
+ document.getElementById('uploadValue').textContent = data.upload_mbps;
310
+ document.getElementById('uploadMBps').textContent = data.upload_MBps;
311
+
312
+ // Update ping values
313
+ document.getElementById('pingValue').textContent = data.ping;
314
+ let pingRating = 'Excellent';
315
+ if (data.ping > 100) {
316
+ pingRating = 'Poor';
317
+ } else if (data.ping > 50) {
318
+ pingRating = 'Average';
319
+ } else if (data.ping > 20) {
320
+ pingRating = 'Good';
321
+ }
322
+ document.getElementById('pingRating').textContent = pingRating;
323
+
324
+ // Update gauges
325
+ updateGauges(data);
326
+ }
327
+
328
+ // Update gauge displays
329
+ function updateGauges(data) {
330
+ // Map values to appropriate ranges for gauges
331
+ const downloadPct = Math.min(data.download_mbps / 150 * 100, 100);
332
+ const uploadPct = Math.min(data.upload_mbps / 100 * 100, 100);
333
+ const pingPct = Math.max(0, Math.min((150 - data.ping) / 150 * 100, 100));
334
+
335
+ // Update download gauge
336
+ downloadGauge.data.datasets[0].data = [downloadPct, 100 - downloadPct];
337
+ downloadGauge.update();
338
+
339
+ // Update upload gauge
340
+ uploadGauge.data.datasets[0].data = [uploadPct, 100 - uploadPct];
341
+ uploadGauge.update();
342
+
343
+ // Update ping gauge
344
+ pingGauge.data.datasets[0].data = [pingPct, 100 - pingPct];
345
+ pingGauge.update();
346
+ }
347
+
348
+ // Initialize gauges
349
+ initGauges();
350
+
351
+ // Event listeners
352
+ startButton.addEventListener('click', startTest);
353
+ retestButton.addEventListener('click', startTest);
354
+ });
355
+ </script>
356
+ </body>
357
+ </html>
templates/testing.html ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Speed Test UI</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ }
12
+
13
+ #progress {
14
+ transform: rotate(-90deg);
15
+ transform-origin: center;
16
+ transition: stroke-dasharray 1s ease-in-out;
17
+ }
18
+
19
+ .glow {
20
+ box-shadow: 0 0 30px #3b82f6, 0 0 60px #3b82f6;
21
+ }
22
+ </style>
23
+ </head>
24
+ <body class="bg-[#0f172a] h-screen flex items-center justify-center">
25
+
26
+ <!-- Container -->
27
+ <div class="relative w-[600px] h-[400px] bg-[#1e293b] rounded-xl shadow-xl glow">
28
+ <!-- Gauge -->
29
+ <div class="flex items-center justify-center h-full">
30
+ <div class="relative">
31
+ <svg class="w-64 h-64" viewBox="0 0 36 36">
32
+ <path class="text-gray-700" fill="none" stroke="currentColor" stroke-width="4" d="M18 2a16 16 0 0 1 0 32 16 16 0 0 1 0-32"></path>
33
+ <path id="progress" fill="none" stroke="#3b82f6" stroke-width="4" stroke-linecap="round" stroke-dasharray="0, 100" d="M18 2a16 16 0 0 1 0 32 16 16 0 0 1 0-32"></path>
34
+ </svg>
35
+ <div class="absolute inset-0 flex items-center justify-center text-white text-5xl font-bold" id="speed">0</div>
36
+ <div class="absolute bottom-5 left-10 text-white">
37
+ <span class="text-sm">Ping</span>
38
+ <div class="text-lg" id="ping">-- ms</div>
39
+ </div>
40
+ <div class="absolute bottom-5 right-10 text-white">
41
+ <span class="text-sm">Download</span>
42
+ <div class="text-lg" id="download">-- Mbps</div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Light Effect -->
48
+ <div class="absolute inset-x-0 bottom-0 h-1/4 bg-gradient-to-t from-blue-600 opacity-75 animate-pulse"></div>
49
+ </div>
50
+
51
+ <script>
52
+ // Fake speed test animation
53
+ const speedElement = document.getElementById('speed');
54
+ const pingElement = document.getElementById('ping');
55
+ const downloadElement = document.getElementById('download');
56
+ const progressElement = document.getElementById('progress');
57
+
58
+ let speed = 0;
59
+ let progress = 0;
60
+
61
+ function animateSpeed() {
62
+ const targetSpeed = Math.floor(Math.random() * 150) + 50; // Random speed between 50-200 Mbps
63
+ const ping = Math.floor(Math.random() * 50) + 10; // Random ping 10-50 ms
64
+ const download = (targetSpeed - Math.random() * 10).toFixed(2);
65
+
66
+ let interval = setInterval(() => {
67
+ if (speed >= targetSpeed) {
68
+ clearInterval(interval);
69
+ } else {
70
+ speed += 2;
71
+ progress = (speed / 200) * 100;
72
+ speedElement.textContent = speed;
73
+ progressElement.style.strokeDasharray = `${progress}, 100`;
74
+ }
75
+ }, 50);
76
+
77
+ setTimeout(() => {
78
+ pingElement.textContent = `${ping} ms`;
79
+ downloadElement.textContent = `${download} Mbps`;
80
+ }, 2000);
81
+ }
82
+
83
+ // Start animation after DOM load
84
+ document.addEventListener('DOMContentLoaded', () => {
85
+ animateSpeed();
86
+ });
87
+ </script>
88
+
89
+ </body>
90
+ </html>