Skip to content

Commit 7f09d11

Browse files
feat: ✨ speedtest card added
1 parent ff2a7f8 commit 7f09d11

5 files changed

Lines changed: 164 additions & 20 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,14 @@ http://localhost:5000
3232
## Why not the docker image?
3333

3434
I have not created the docker image for this project because it requires the access to the host machine to get the server stats. So, it is not possible to get the server stats from the docker container.
35+
36+
37+
## Acknowledgments
38+
39+
- This project uses `speedtest-cli` for performing speed tests. `speedtest-cli` is licensed under the Apache License 2.0. For more details, please refer to its [GitHub repository](https://github.com/sivel/speedtest-cli).
40+
41+
- This project uses `psutil` for getting the system stats. `psutil` is licensed under the BSD 3-Clause "New" or "Revised" License. For more details, please refer to its [GitHub repository](https://github.com/giampaolo/psutil)
42+
43+
- This project uses `flask` for creating the web app. `flask` is licensed under the BSD 3-Clause "New" or "Revised" License. For more details, please refer to its [GitHub repository](https://github.com/pallets/flask)
44+
45+
- This project uses `chart.js` for creating the charts. `chart.js` is licensed under the MIT License. For more details, please refer to its [GitHub repository](https://github.com/chartjs/Chart.js)

app.py

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,43 @@
11
from flask import Flask, render_template
2+
from flask_sqlalchemy import SQLAlchemy
23
import os
34
import psutil
45
import datetime
6+
import subprocess
57

68
app = Flask(__name__)
79

10+
# Configure the SQLite database
11+
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///speedtest_results.db'
12+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
13+
14+
# Initialize the database
15+
db = SQLAlchemy(app)
16+
17+
# Define the model for storing speed test results
18+
class SpeedTestResult(db.Model):
19+
id = db.Column(db.Integer, primary_key=True)
20+
download_speed = db.Column(db.String(50))
21+
upload_speed = db.Column(db.String(50))
22+
ping = db.Column(db.String(50))
23+
timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow)
24+
25+
def __repr__(self):
26+
return f'<SpeedTestResult {self.download_speed}, {self.upload_speed}, {self.ping}>'
27+
828
def get_established_connections():
9-
connection = psutil.net_connections()
10-
ipv4_dict = {}
11-
ipv6_dict = {}
29+
connections = psutil.net_connections()
30+
ipv4_dict = set()
31+
ipv6_dict = set()
1232

13-
for conn in connection:
33+
for conn in connections:
1434
if conn.status == 'ESTABLISHED':
1535
if '.' in conn.laddr.ip:
16-
ipv4_dict = conn.laddr.ip
36+
ipv4_dict.add(conn.laddr.ip)
1737
elif ':' in conn.laddr.ip:
18-
ipv6_dict = conn.laddr.ip
38+
ipv6_dict.add(conn.laddr.ip)
39+
40+
ipv4_dict = [ip for ip in ipv4_dict if ip.startswith('192.168')][0]
1941

2042
return ipv4_dict, ipv6_dict
2143

@@ -31,32 +53,85 @@ def change_up_time_format(uptime):
3153
def get_system_info():
3254
info = {
3355
'username': os.getlogin(),
34-
'cpu_percent': psutil.cpu_percent(interval=1),
35-
'memory_percent': psutil.virtual_memory().percent,
36-
'disk_usage': psutil.disk_usage('/').percent,
56+
'cpu_percent': round(psutil.cpu_percent(interval=1), 2),
57+
'memory_percent': round(psutil.virtual_memory().percent, 2),
58+
'disk_usage': round(psutil.disk_usage('/').percent, 2),
3759
'battery_percent': round(psutil.sensors_battery().percent) if psutil.sensors_battery() else "N/A",
3860
'cpu_core': psutil.cpu_count(),
3961
'boot_time': datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S"),
4062
'network_sent': round(psutil.net_io_counters().bytes_sent / (1024 ** 2), 2), # In MB
4163
'network_received': round(psutil.net_io_counters().bytes_recv / (1024 ** 2), 2), # In MB
4264
'process_count': len(psutil.pids()),
4365
'swap_memory': psutil.swap_memory().percent,
44-
'uptime': change_up_time_format(datetime.datetime.now() - datetime.datetime.fromtimestamp(psutil.boot_time()))
66+
'uptime': change_up_time_format(datetime.datetime.now() - datetime.datetime.fromtimestamp(psutil.boot_time())),
67+
'ipv4_connections': get_established_connections()[0],
68+
'ipv6_connections': get_established_connections()[1]
4569
}
46-
47-
ipv4_conn, ipv6_conn = get_established_connections()
48-
info['ipv4_connections'] = ipv4_conn
49-
info['ipv6_connections'] = ipv6_conn
5070
return info
5171

72+
def run_speedtest():
73+
try:
74+
# Run the speedtest and capture the output
75+
result = subprocess.run(['speedtest-cli'], capture_output=True, text=True, check=True)
76+
77+
# Extract relevant information from the output
78+
output_lines = result.stdout.splitlines()
79+
download_speed = None
80+
upload_speed = None
81+
ping = None
82+
83+
for line in output_lines:
84+
if "Download:" in line:
85+
download_speed = line.split("Download: ")[1]
86+
elif "Upload:" in line:
87+
upload_speed = line.split("Upload: ")[1]
88+
elif "Ping:" in line:
89+
ping = line.split("Ping: ")[1]
90+
91+
return {"download_speed": download_speed, "upload_speed": upload_speed, "ping": ping}
92+
93+
except subprocess.CalledProcessError as e:
94+
print(f"Speedtest failed with error: {e.stderr}")
95+
return None
96+
97+
except Exception as e:
98+
print(f"Error occurred while running speed test: {e}")
99+
return None
100+
101+
@app.route('/speedtest')
102+
def speedtest():
103+
# Check if the database has three or more entries in the last hour
104+
one_hour_ago = datetime.datetime.utcnow() - datetime.timedelta(hours=1)
105+
recent_results = SpeedTestResult.query.filter(SpeedTestResult.timestamp > one_hour_ago).all()
106+
107+
if len(recent_results) < 3:
108+
# Run a new speed test and store the result
109+
speedtest_result = run_speedtest()
110+
if speedtest_result:
111+
new_result = SpeedTestResult(
112+
download_speed=speedtest_result['download_speed'],
113+
upload_speed=speedtest_result['upload_speed'],
114+
ping=speedtest_result['ping']
115+
)
116+
db.session.add(new_result)
117+
db.session.commit()
118+
return render_template('speedtest_result.html', speedtest_result=speedtest_result, source="Actual Test")
119+
else:
120+
# Retrieve the latest result from the database
121+
latest_result = recent_results[-1]
122+
next_test_time = one_hour_ago + datetime.timedelta(hours=1)
123+
return render_template('speedtest_result.html',
124+
speedtest_result=latest_result,
125+
source="Database",
126+
next_test_time=next_test_time)
127+
52128
@app.route('/')
53129
def dashboard():
54130
system_info = get_system_info()
55131
return render_template('dashboard.html', system_info=system_info)
56132

57133
@app.route('/cpu_usage')
58134
def cpu_usage():
59-
# Detailed CPU usage stats
60135
cpu_usage = psutil.cpu_percent(interval=1, percpu=True)
61136
return render_template('cpu_usage.html', cpu_usage=cpu_usage)
62137

@@ -90,10 +165,11 @@ def network_stats():
90165

91166
@app.route('/system_health')
92167
def system_health():
93-
# Reuse system_info function for summary
94168
system_info = get_system_info()
95169
return render_template('system_health.html', system_info=system_info)
96170

97-
98171
if __name__ == '__main__':
172+
# Create the database tables if they don't exist
173+
with app.app_context():
174+
db.create_all()
99175
app.run(host='0.0.0.0', port=5000, debug=True)

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
Flask==2.3.3
22
psutil==5.9.5
33
gunicorn
4+
speetest-cli
5+
flask-sqlalchemy

templates/dashboard.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ <h5 class="card-title">Username <i class="fas fa-user"></i></h5>
1818
<div class="col-md-6 col-lg-4 mb-4">
1919
<div class="card shadow-sm border-0 rounded-3">
2020
<div class="card-body">
21-
<h5 class="card-title">IPV4 <i class="fas fa-network-wired"></i></h5>
21+
<h5 class="card-title">IPV4 Connections <i class="fas fa-network-wired"></i></h5>
2222
<p class="card-text fs-4">{{ system_info['ipv4_connections'] }}</p>
2323
</div>
2424
</div>
2525
</div>
2626

2727
<div class="col-md-6 col-lg-4 mb-4">
28-
<div class="card shadow-sm border-0 rounded-3 battery-card" data-battery="{{ system_info['battery_percent'] }}">
28+
<div class="card shadow-sm border-0 rounded-3">
2929
<div class="card-body">
3030
<h5 class="card-title">Battery Percentage <i class="fas fa-battery-three-quarters"></i></h5>
3131
<p class="card-text fs-4">{{ system_info['battery_percent'] }}%</p>
@@ -36,7 +36,7 @@ <h5 class="card-title">Battery Percentage <i class="fas fa-battery-three-quarter
3636
<div class="col-md-6 col-lg-4 mb-4">
3737
<div class="card shadow-sm border-0 rounded-3">
3838
<div class="card-body">
39-
<h5 class="card-title">CPU Core <i class="fas fa-microchip"></i></h5>
39+
<h5 class="card-title">CPU Cores <i class="fas fa-microchip"></i></h5>
4040
<p class="card-text fs-4">{{ system_info['cpu_core'] }}</p>
4141
</div>
4242
</div>
@@ -103,6 +103,11 @@ <h5 class="card-title">Network Statistics <i class="fas fa-network-wired"></i></
103103
</div>
104104
</div>
105105
</div>
106+
107+
<!-- Speed Test Button -->
108+
<div class="col-md-12 mb-4 text-center">
109+
<a href="{{ url_for('speedtest') }}" class="btn btn-primary btn-lg">Run Speed Test</a>
110+
</div>
106111
</div>
107112
</div>
108113
{% endblock %}

templates/speedtest_result.html

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{% extends 'base.html' %}
2+
3+
{% block title %}Speed Test Result{% endblock %}
4+
5+
{% block content %}
6+
<div class="container mt-5">
7+
<div class="row justify-content-center">
8+
<div class="col-md-8">
9+
<div class="card shadow-sm border-0 rounded-3">
10+
<div class="card-body">
11+
<h1 class="card-title text-center mb-4">Speed Test Result</h1>
12+
{% if speedtest_result %}
13+
<ul class="list-group list-group-flush">
14+
<li class="list-group-item d-flex justify-content-between align-items-center">
15+
<strong>Download Speed:</strong>
16+
<span>{{ speedtest_result.download_speed }}</span>
17+
</li>
18+
<li class="list-group-item d-flex justify-content-between align-items-center">
19+
<strong>Upload Speed:</strong>
20+
<span>{{ speedtest_result.upload_speed }}</span>
21+
</li>
22+
<li class="list-group-item d-flex justify-content-between align-items-center">
23+
<strong>Ping:</strong>
24+
<span>{{ speedtest_result.ping }}</span>
25+
</li>
26+
<li class="list-group-item d-flex justify-content-between align-items-center">
27+
<strong>Source:</strong>
28+
<span>{{ source }}</span>
29+
</li>
30+
{% if source == "Database" %}
31+
<li class="list-group-item d-flex justify-content-between align-items-center">
32+
<strong>Next Test Time:</strong>
33+
<span>{{ next_test_time }}</span>
34+
</li>
35+
{% endif %}
36+
</ul>
37+
{% else %}
38+
<div class="alert alert-warning text-center" role="alert">
39+
No speed test results available.
40+
</div>
41+
{% endif %}
42+
<div class="text-center mt-4">
43+
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Back to Dashboard</a>
44+
</div>
45+
</div>
46+
</div>
47+
</div>
48+
</div>
49+
</div>
50+
{% endblock %}

0 commit comments

Comments
 (0)