Skip to content

Commit 868b6ce

Browse files
chore: website ping time in human readable format
1 parent 2cf1c1a commit 868b6ce

File tree

7 files changed

+322
-35
lines changed

7 files changed

+322
-35
lines changed

app.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
from src.config import app
22
from src import routes
3-
from src.thread_process import monitor_settings, start_website_monitoring
3+
from src.background_task import start_website_monitoring, monitor_settings
44

55
# background thread to monitor system settings changes
6-
# monitor_settings() # Starts monitoring for system logging changes
7-
# start_website_monitoring() # Starts pinging active websites
6+
monitor_settings() # Starts monitoring for system logging changes
7+
start_website_monitoring() # Starts pinging active websites
88

99
if __name__ == "__main__":
10-
11-
# Run the Flask application
12-
app.run(host="0.0.0.0", port=5000, debug=True)
10+
app.run(host="0.0.0.0", port=5000, debug=False)

src/background_task/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from src.background_task.monitor_website import start_website_monitoring
2+
from src.background_task.log_system_info import monitor_settings
3+
4+
__all__ = ["start_website_monitoring", "monitor_settings"]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import os
2+
import datetime
3+
from threading import Timer
4+
from src.config import app, db
5+
from src.utils import _get_system_info
6+
from src.logger import logger
7+
from src.models import GeneralSettings, SystemInformation
8+
from sqlalchemy.exc import SQLAlchemyError
9+
from src.logger import logger
10+
11+
# Flag to check if logging is already scheduled
12+
is_logging_scheduled = False
13+
14+
15+
def log_system_info():
16+
"""
17+
Logs system information at regular intervals based on the general settings.
18+
This function checks if logging is still active before each logging event.
19+
"""
20+
global is_logging_scheduled
21+
with app.app_context():
22+
try:
23+
# Fetch the general settings to check if logging is enabled
24+
general_settings = GeneralSettings.query.first()
25+
is_logging_system_info = (
26+
general_settings.is_logging_system_info if general_settings else False
27+
)
28+
29+
if not is_logging_system_info:
30+
logger.info("System info logging has been stopped.")
31+
is_logging_scheduled = False # Reset the flag if logging stops
32+
return
33+
34+
log_system_info_to_db()
35+
logger.debug("System information logged successfully.")
36+
37+
# Schedule the next log after 60 seconds
38+
Timer(60, log_system_info).start()
39+
40+
except Exception as e:
41+
logger.error(f"Error during system info logging: {e}", exc_info=True)
42+
is_logging_scheduled = False # Reset the flag in case of an error
43+
44+
45+
def log_system_info_to_db():
46+
"""
47+
Fetches system information and logs it to the database.
48+
"""
49+
with app.app_context():
50+
try:
51+
system_info = _get_system_info()
52+
system_log = SystemInformation(
53+
cpu_percent=system_info["cpu_percent"],
54+
memory_percent=system_info["memory_percent"],
55+
battery_percent=system_info["battery_percent"],
56+
network_sent=system_info["network_sent"],
57+
network_received=system_info["network_received"],
58+
dashboard_memory_usage=system_info["dashboard_memory_usage"],
59+
cpu_frequency=system_info["cpu_frequency"],
60+
current_temp=system_info["current_temp"],
61+
timestamp=datetime.datetime.now(),
62+
)
63+
db.session.add(system_log)
64+
db.session.commit()
65+
logger.info("System information logged to database.")
66+
67+
except SQLAlchemyError as db_err:
68+
logger.error(
69+
f"Database error while logging system info: {db_err}", exc_info=True
70+
)
71+
db.session.rollback()
72+
except Exception as e:
73+
logger.error(f"Failed to log system information: {e}", exc_info=True)
74+
75+
def monitor_settings():
76+
"""
77+
Monitors application general settings for changes and controls system logging dynamically.
78+
This function runs periodically to check for updates to logging settings.
79+
"""
80+
global is_logging_scheduled
81+
with app.app_context():
82+
try:
83+
# Fetch the general settings
84+
general_settings = GeneralSettings.query.first()
85+
86+
# Check if logging should be active or not
87+
is_logging_system_info = (
88+
general_settings.is_logging_system_info if general_settings else False
89+
)
90+
if is_logging_system_info:
91+
logger.info("System logging enabled. Starting system info logging.")
92+
93+
# Schedule logging only if not already scheduled
94+
if not is_logging_scheduled:
95+
logger.debug("Scheduling system info logging.")
96+
Timer(0, log_system_info).start()
97+
is_logging_scheduled = True
98+
else:
99+
logger.info("System logging disabled. Stopping system info logging.")
100+
is_logging_scheduled = False # Reset the flag if logging is disabled
101+
102+
# Check settings periodically (every 10 seconds)
103+
Timer(10, monitor_settings).start()
104+
105+
except SQLAlchemyError as db_err:
106+
logger.error(f"Error fetching settings: {db_err}", exc_info=True)
107+
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import os
2+
import datetime
3+
from threading import Timer
4+
from flask import url_for
5+
from src.config import app, db
6+
from src.utils import _get_system_info
7+
from src.logger import logger
8+
from src.models import MonitoredWebsite, GeneralSettings, SystemInformation
9+
from sqlalchemy.exc import SQLAlchemyError
10+
import requests
11+
from src.scripts.email_me import send_smtp_email
12+
from src.logger import logger
13+
from src.utils import render_template_from_file, ROOT_DIR
14+
from src.config import get_app_info
15+
16+
# Dictionary to track the last known status of each website
17+
website_status = {}
18+
19+
def send_mail(website_name, status, email_adress, email_alerts_enabled):
20+
"""
21+
Dummy function to simulate sending an email.
22+
23+
Args:
24+
website_name (str): The name or URL of the website.
25+
status (str): The status of the website, either 'DOWN' or 'UP'.
26+
"""
27+
# This is a dummy function, so no real email is sent.
28+
if email_alerts_enabled:
29+
context = {
30+
"website_status": status, # UP/DOWN
31+
"website_name": website_name,
32+
"checked_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
33+
"message": f"{website_name} is now {status}",
34+
"title": get_app_info()["title"],
35+
}
36+
website_status_template = os.path.join(
37+
ROOT_DIR, "src/templates/email_templates/website_monitor_status.html"
38+
)
39+
email_subject = f"{website_name} is now {status}"
40+
email_body = render_template_from_file(website_status_template, **context)
41+
send_smtp_email(email_adress, email_subject, email_body, is_html=True)
42+
43+
44+
def update_website_status(website, status):
45+
"""
46+
Updates the status of the website and sends an email notification if the status has changed.
47+
48+
Args:
49+
website (MonitoredWebsite): The website object to update.
50+
status (str): The new status of the website.
51+
"""
52+
global website_status
53+
54+
if website.id not in website_status:
55+
website_status[website.id] = "UP" # Initialize with UP status if not present
56+
57+
if website_status[website.id] != status:
58+
send_mail(
59+
website.name, status, website.email_address, website.email_alerts_enabled
60+
)
61+
website_status[website.id] = status
62+
63+
64+
def ping_website(website):
65+
"""
66+
Pings a single website and updates its status in the database.
67+
68+
Args:
69+
website (MonitoredWebsite): The website object to ping.
70+
"""
71+
with app.app_context():
72+
try:
73+
# Check if the website is still active
74+
updated_website = MonitoredWebsite.query.get(website.id)
75+
if not updated_website or not updated_website.is_ping_active:
76+
logger.info(
77+
f"Website {website.name} is no longer active. Stopping monitoring."
78+
)
79+
return
80+
81+
logger.info(
82+
f"Pinging {website.name} (Interval: {website.ping_interval}s)..."
83+
)
84+
response = requests.get(website.name, timeout=10)
85+
updated_website.last_ping_time = datetime.datetime.now()
86+
updated_website.ping_status_code = response.status_code
87+
88+
new_status = "UP" if response.status_code == 200 else "DOWN"
89+
updated_website.ping_status = new_status
90+
91+
# Update the website status
92+
db.session.commit()
93+
logger.info(f"Website {website.name} updated successfully.")
94+
95+
# Determine if an email should be sent
96+
update_website_status(website, new_status)
97+
98+
except requests.RequestException as req_err:
99+
updated_website.ping_status = "DOWN"
100+
logger.error(f"Failed to ping {website.name}: {req_err}", exc_info=True)
101+
db.session.rollback()
102+
103+
except SQLAlchemyError as db_err:
104+
logger.error(
105+
f"Database commit error for {website.name}: {db_err}", exc_info=True
106+
)
107+
db.session.rollback()
108+
109+
finally:
110+
# Add more detailed logging for debugging
111+
if db.session.new or db.session.dirty:
112+
logger.warning(
113+
f"Database transaction not committed properly for {website.name}."
114+
)
115+
116+
# Schedule the next ping for this website
117+
Timer(
118+
updated_website.ping_interval, ping_website, args=[updated_website]
119+
).start()
120+
121+
def start_website_monitoring():
122+
"""
123+
Periodically pings monitored websites based on individual ping intervals.
124+
"""
125+
with app.app_context():
126+
try:
127+
while True:
128+
active_websites = MonitoredWebsite.query.filter_by(
129+
is_ping_active=True
130+
).all()
131+
if not active_websites:
132+
logger.info("No active websites to monitor.")
133+
else:
134+
for website in active_websites:
135+
# Start pinging each website individually based on its ping interval
136+
Timer(0, ping_website, args=[website]).start()
137+
138+
# Check for active websites periodically (every 30 seconds)
139+
Timer(30, start_website_monitoring).start()
140+
break # Break out of the loop to avoid creating a new thread infinitely
141+
142+
except SQLAlchemyError as db_err:
143+
logger.error(
144+
f"Database error during website monitoring: {db_err}", exc_info=True
145+
)
146+
except Exception as e:
147+
logger.error(f"Error during website monitoring: {e}", exc_info=True)

src/thread_process.py renamed to src/background_task/thread_process.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# deprecated file
2+
13
import os
24
import datetime
35
from threading import Timer
@@ -121,6 +123,33 @@ def ping_website(website):
121123
updated_website.ping_interval, ping_website, args=[updated_website]
122124
).start()
123125

126+
def start_website_monitoring():
127+
"""
128+
Periodically pings monitored websites based on individual ping intervals.
129+
"""
130+
with app.app_context():
131+
try:
132+
while True:
133+
active_websites = MonitoredWebsite.query.filter_by(
134+
is_ping_active=True
135+
).all()
136+
if not active_websites:
137+
logger.info("No active websites to monitor.")
138+
else:
139+
for website in active_websites:
140+
# Start pinging each website individually based on its ping interval
141+
Timer(0, ping_website, args=[website]).start()
142+
143+
# Check for active websites periodically (every 30 seconds)
144+
Timer(30, start_website_monitoring).start()
145+
break # Break out of the loop to avoid creating a new thread infinitely
146+
147+
except SQLAlchemyError as db_err:
148+
logger.error(
149+
f"Database error during website monitoring: {db_err}", exc_info=True
150+
)
151+
except Exception as e:
152+
logger.error(f"Error during website monitoring: {e}", exc_info=True)
124153

125154
def monitor_settings():
126155
"""
@@ -217,30 +246,3 @@ def log_system_info_to_db():
217246
logger.error(f"Failed to log system information: {e}", exc_info=True)
218247

219248

220-
def start_website_monitoring():
221-
"""
222-
Periodically pings monitored websites based on individual ping intervals.
223-
"""
224-
with app.app_context():
225-
try:
226-
while True:
227-
active_websites = MonitoredWebsite.query.filter_by(
228-
is_ping_active=True
229-
).all()
230-
if not active_websites:
231-
logger.info("No active websites to monitor.")
232-
else:
233-
for website in active_websites:
234-
# Start pinging each website individually based on its ping interval
235-
Timer(0, ping_website, args=[website]).start()
236-
237-
# Check for active websites periodically (every 30 seconds)
238-
Timer(30, start_website_monitoring).start()
239-
break # Break out of the loop to avoid creating a new thread infinitely
240-
241-
except SQLAlchemyError as db_err:
242-
logger.error(
243-
f"Database error during website monitoring: {db_err}", exc_info=True
244-
)
245-
except Exception as e:
246-
logger.error(f"Error during website monitoring: {e}", exc_info=True)

src/routes/ping.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,40 @@
88

99
ping_bp = blueprints.Blueprint('ping', __name__)
1010

11+
def time_ago(ping_time, current_time):
12+
"""
13+
Calculate the time difference between the current time and the ping time.
14+
Return a human-readable format like '1 min ago' or '30 seconds ago'.
15+
16+
Args:
17+
ping_time (datetime): The last ping time.
18+
current_time (datetime): The current time.
19+
20+
Returns:
21+
str: A formatted string representing the time ago.
22+
"""
23+
if not ping_time:
24+
return "Never"
25+
26+
# Calculate the difference in seconds
27+
time_diff = (current_time - ping_time).total_seconds()
28+
29+
if time_diff <= 120: # Less than or equal to 2 minutes
30+
if time_diff <= 60:
31+
return f"{int(time_diff)} seconds ago"
32+
return f"{int(time_diff // 60)} min ago"
33+
34+
# Fallback to formatted datetime if over 2 minutes
35+
return ping_time.strftime('%Y-%m-%d %H:%M:%S')
36+
1137
# Route to view and add websites
1238
@app.route('/monitor_websites')
1339
@login_required
1440
def monitor_websites():
1541
websites = MonitoredWebsite.query.all()
42+
current_time = datetime.datetime.now()
43+
for website in websites:
44+
website.last_ping_time_ago = time_ago(website.last_ping_time, current_time)
1645
return render_template('ping/ping.html', websites=websites)
1746

1847
# Route to add a website

0 commit comments

Comments
 (0)