Skip to content

feature change for dev branch to test on dev enviornment #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 6, 2024
57 changes: 39 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,25 @@ chmod +x install_miniconda.sh && sudo ./install_miniconda.sh

## Features 🚀

- Monitor server stats like CPU, Memory, Disk, and Network.
- Check the network speed of the server using a speed test.
- Rate limit the speed test to prevent abuse.
- Kill the process that is consuming the most CPU.
- Real-time monitoring of server stats.
- Responsive design that works on mobile, tablet, and desktop.
- Update itself to the latest version.
- Easy download and installation using a bash script.
- Logged user and admin user will get the notification if the user kill some process manully on dashbaord.
- Different email alerts for different actions.
- Different Dashboards for different users.(Deveoper, Admin, IT Manager, Manager)
- Lightweight, open-source, and free to use with a straightforward installation process.
- Capable of monitoring core server metrics like CPU, memory, disk usage, and network traffic.
- Includes built-in security features such as authentication for login, logout, and signup.
- Administrators can manage user accounts by creating, updating, or deleting users.
- Admin-level access is required for configuring settings, managing users, and adjusting security and notification - preferences.
- Historical performance data can be viewed as charts, aiding in trend analysis.
- Supports network speed testing directly from the server.
- Provides the ability to terminate resource-heavy processes with a single command.
- Real-time server metric monitoring keeps data consistently updated.
- The interface is responsive and optimized for various devices including mobile, tablets, and desktops.
- The system can automatically update to the latest version to simplify maintenance.
- Installation can be done quickly via a bash script for easy setup.
- Notifications are sent to users and admins when a process is manually terminated.
- Offers website monitoring tasks that trigger email alerts when a website becomes unavailable.
- Configurable email alerts for various actions across the server.
- Role-based dashboards tailored for Developer, Admin, IT Manager, and Manager roles (upcoming feature).
- Option to download historical data in CSV format for detailed analysis (upcoming feature).
- Server status monitoring with alerts for server downtime or recovery (upcoming feature).


## Email Feature 📧

Expand All @@ -81,7 +89,9 @@ chmod +x install_miniconda.sh && sudo ./install_miniconda.sh
| Server Up | Yes | Admin User |
| Notification Settings Change | Yes | Admin User |
| Signup | Yes(few changes required) | Admin User & Logged User |

| Website Monitoring | Yes | Input Email |
| Server Down | No | Admin User |
| Server Up | No | Admin User |


## Product Screenshots 📸
Expand Down Expand Up @@ -112,12 +122,23 @@ A Docker image has not been created for this project because it requires access

## Upcoming Features 📅

- Threshold notifications
- Customizable dashboards
- Plugin support to make SystemGuard even more powerful.
- make server logs
- Check Disk read/write speed
- Check Firewall status
- Receive notifications when system metrics cross predefined thresholds.
- Customizable dashboards for personalized server monitoring.
- Plugin support to enhance and extend SystemGuard's functionality.
- Generate and manage server logs for better tracking and troubleshooting.
- Monitor disk read/write speeds for performance insights.
- Check the current firewall status to ensure security.
- A dedicated website monitoring page for tracking uptime and performance.
- Track and save total network data sent/received in the database for data usage monitoring.

## Learnings 📖

- How to use the `psutil` library to retrieve system stats.
- How to use multiple Flask routes to display different pages.
- How to use multi-threading to run a function in the background.
- How to use the `chart.js` library to display charts on a webpage.
- How to use the `flask-login` library to manage user sessions.
- How to use the `flask-mail` library to send emails in Flask.

## Acknowledgments

Expand Down
24 changes: 15 additions & 9 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import os
import time
import datetime
from src.config import app
import threading
from src.config import app, db
from src import routes
from src.utils import render_template_from_file, get_flask_memory_usage, cpu_usage_percent, get_memory_percent, ROOT_DIR
from src.models import UserProfile
from src.scripts.email_me import send_smpt_email
from src.utils import get_system_info_for_db
from src.models import SystemInformation, ApplicationGeneralSettings
from sqlalchemy.exc import SQLAlchemyError
from src.logger import logger
from src.thread_process import monitor_settings, start_website_monitoring

def register_routes():
app.register_blueprint(routes.dashboard_bp)
Expand All @@ -17,14 +21,16 @@ def register_routes():
app.register_blueprint(routes.speedtest_bp)
app.register_blueprint(routes.process_bp)

app.config['is_server_up_email_sent'] = False




if __name__ == "__main__":
register_routes()
# # Start the memory-consuming program in a separate thread
# memory_thread = threading.Thread(target=memory_consuming_program, daemon=True)
# memory_thread.start()


# Start monitoring settings and website pinging when the server starts
# monitor_settings() # Starts monitoring for system logging changes
# start_website_monitoring() # Starts pinging active websites

# Run the Flask application
app.run(host="0.0.0.0", port=5000, debug=True)
3 changes: 2 additions & 1 deletion setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ create_dir_if_not_exists() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir" || { log "ERROR" "Failed to create directory: $dir"; exit 1; }
chown "$USER_NAME:$USER_NAME" "$dir" || { log "ERROR" "Failed to change ownership of directory: $dir"; exit 1; }
fi
}

Expand Down Expand Up @@ -673,7 +674,7 @@ fix() {
if lsof -Pi :5050 -sTCP:LISTEN -t >/dev/null; then
kill -9 $(lsof -t -i:5050)
log "Restarting server... at $EXTRACT_DIR/${APP_NAME}-*/src"
log "Server started successfully in the background, user can check the logs using --server-logs"
log "Server is starting in the background, check logs for more details."
log "Server will be running on $HOST_URL"
else
log "Server is not running."
Expand Down
33 changes: 33 additions & 0 deletions src/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import logging
from logging.handlers import RotatingFileHandler
import os

# Create a logs directory if it doesn't exist
if not os.path.exists('logs'):
os.makedirs('logs')

# Log Formatter with datetime, log level, and message
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)

# File handler for logging to a file
file_handler = RotatingFileHandler(
'logs/app_debug.log',
maxBytes=10 * 1024 * 1024, # 10 MB per log file
backupCount=5 # Keep up to 5 old log files
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)

# Set up the logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # Log everything (DEBUG, INFO, WARNING, etc.)
logger.addHandler(file_handler)

# # Optionally, add console output for real-time debugging (optional)
# console_handler = logging.StreamHandler()
# console_handler.setLevel(logging.INFO)
# console_handler.setFormatter(formatter)
# logger.addHandler(console_handler)
1 change: 1 addition & 0 deletions src/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from src.models.network_speed_test_result import NetworkSpeedTestResult
from src.models.system_information import SystemInformation
from src.models.user_profile import UserProfile
from src.models.monitored_website import MonitoredWebsite
from flask_login import current_user
from werkzeug.security import generate_password_hash
import json
Expand Down
2 changes: 1 addition & 1 deletion src/models/application_general_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ class ApplicationGeneralSettings(db.Model):
enable_alerts = db.Column(db.Boolean, default=True)
timezone = db.Column(db.String(50), default='UTC')
enable_cache = db.Column(db.Boolean, default=True)

is_logging_system_info = db.Column(db.Boolean, default=True)
16 changes: 16 additions & 0 deletions src/models/monitored_website.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from src.config import db


class MonitoredWebsite(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
ping_interval = db.Column(db.Integer, nullable=False) # in seconds
is_ping_active = db.Column(db.Boolean, default=True) # True: ping the site, False: do not ping
email_address = db.Column(db.String(255), nullable=True)
ping_status = db.Column(db.String(50), nullable=True) # Stores 'UP' or 'DOWN'
ping_status_code = db.Column(db.Integer, nullable=True)
last_ping_time = db.Column(db.DateTime, nullable=True)
email_alerts_enabled = db.Column(db.Boolean, default=False)

def __repr__(self):
return f'<Website {self.name} - Ping Every {self.ping_interval}s>'
27 changes: 7 additions & 20 deletions src/models/system_information.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,10 @@
class SystemInformation(db.Model):
__tablename__ = "system_information"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50))
cpu_percent = db.Column(db.Float)
memory_percent = db.Column(db.Float)
disk_usage = db.Column(db.Float)
battery_percent = db.Column(db.Float)
cpu_core = db.Column(db.Integer)
boot_time = db.Column(db.String(50))
network_sent = db.Column(db.Float)
network_received = db.Column(db.Float)
process_count = db.Column(db.Integer)
swap_memory = db.Column(db.Float)
uptime = db.Column(db.String(50))
ipv4_connections = db.Column(db.String(50))
dashboard_memory_usage = db.Column(db.String(50))
timestamp = db.Column(db.DateTime, default=datetime.datetime.now())
cpu_frequency = db.Column(db.String(50))
current_temp = db.Column(db.String(50))

def __repr__(self):
return f"<SystemInfo {self.username}, {self.cpu_percent}, {self.memory_percent}, {self.disk_usage}, {self.battery_percent}, {self.cpu_core}, {self.boot_time}, {self.network_sent}, {self.network_received}, {self.process_count}, {self.swap_memory}, {self.uptime}, {self.ipv4_connections}, {self.ipv6_connections}, {self.dashboard_memory_usage}>"
cpu_percent = db.Column(db.Float, nullable=False)
memory_percent = db.Column(db.Float, nullable=False)
battery_percent = db.Column(db.Float, nullable=False)
network_sent = db.Column(db.Float, nullable=False)
network_received = db.Column(db.Float, nullable=False)
timestamp = db.Column(db.DateTime, nullable=False)

3 changes: 2 additions & 1 deletion src/models/user_dashboard_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ class UserDashboardSettings(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
speedtest_cooldown = db.Column(db.Integer, default=60)
number_of_speedtests = db.Column(db.Integer, default=1)
refresh_interval = db.Column(db.Integer, default=10)
refresh_interval = db.Column(db.Integer, default=0)

6 changes: 4 additions & 2 deletions src/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from src.routes.cpu_info import cpu_info_bp
from src.routes.disk_info import disk_info_bp
from src.routes.dashbaord import dashboard_bp
from src.routes.dashboard import dashboard_bp
from src.routes.memory_info import memory_info_bp
from src.routes.network_info import network_info_bp
from src.routes.settings import settings_bp
Expand All @@ -11,4 +11,6 @@
from src.routes.network import network_bp
from src.routes.other import other_bp
from src.routes.smtp_email_config import smtp_email_config_bp
from src.routes.user import user_bp
from src.routes.user import user_bp
from src.routes.graphs import graphs_bp
from src.routes.ping import ping_bp
14 changes: 12 additions & 2 deletions src/routes/cpu_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from src.config import app
from src.utils import get_cpu_core_count, get_cpu_frequency, cpu_usage_percent, get_cpu_temp, get_cached_value
from src.models import PageToggleSettings
from src.models import PageToggleSettings, SystemInformation

cpu_info_bp = blueprints.Blueprint("cpu_usage", __name__)

Expand All @@ -27,4 +27,14 @@ def cpu_usage():
"high_temp": high_temp,
"critical_temp": critical_temp,
}
return render_template("info_pages/cpu_info.html", system_info=system_info)

recent_system_info_entries = SystemInformation.query.all()
if recent_system_info_entries:
cpu_data = [info.cpu_percent for info in recent_system_info_entries]
time_data = [info.timestamp for info in recent_system_info_entries]
else:
cpu_data = []
time_data = []

return render_template("info_pages/cpu_info.html", system_info=system_info,
cpu=cpu_data, time=time_data)
9 changes: 7 additions & 2 deletions src/routes/dashbaord.py → src/routes/dashboard.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import datetime
from flask import render_template, blueprints
from flask import render_template, blueprints, jsonify
from flask_login import login_required, current_user

from src.config import app
Expand Down Expand Up @@ -59,4 +59,9 @@ def dashboard():
last_timestamp=last_speedtest_timestamp,
current_user=current_user,
)



# health page
@app.route("/health", methods=["GET"])
def health():
return jsonify({"status": "ok"})
2 changes: 1 addition & 1 deletion src/routes/disk_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def disk_usage():
return render_template("error/403.html")
disk_total = get_cached_value("disk_total", get_disk_total)
system_info = {
"disk_usage": get_disk_usage_percent(),
"disk_percent": get_disk_usage_percent(),
"disk_total": disk_total,
"disk_used": get_disk_used(),
"disk_free": get_disk_free(),
Expand Down
33 changes: 33 additions & 0 deletions src/routes/graphs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from flask import render_template, blueprints
from src.config import app
from src.models import SystemInformation

graphs_bp = blueprints.Blueprint("graphs", __name__)


@app.route('/graphs')
def graphs():
# Query the last 3 entries from the SystemInformation table
recent_system_info_entries = SystemInformation.query.all()

if recent_system_info_entries:
# Extract data from the query results
time_data = [info.timestamp for info in recent_system_info_entries]
cpu_data = [info.cpu_percent for info in recent_system_info_entries]
memory_data = [info.memory_percent for info in recent_system_info_entries]
battery_data = [info.battery_percent for info in recent_system_info_entries]
network_sent_data = [info.network_sent for info in recent_system_info_entries]
network_received_data = [info.network_received for info in recent_system_info_entries]
else:
time_data = []
cpu_data = []
memory_data = []
battery_data = []
network_sent_data = []
network_received_data = []

# Pass the data to the template
return render_template('graphs.html', cpu=cpu_data, time=time_data,
memory=memory_data, battery=battery_data,
network_sent=network_sent_data,
network_received=network_received_data)
22 changes: 9 additions & 13 deletions src/routes/helper.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
from src.models import UserProfile
from src.config import app


def get_email_addresses(user_level=None, receive_email_alerts=True, fetch_all_users=False):
"""Retrieve email addresses of users based on filters."""
with app.app_context():
# Build query filter based on the presence of `user_level`
filters = []
# Build query with filters
query = UserProfile.query
if user_level:
filters.append(UserProfile.user_level == user_level)
query = query.filter(UserProfile.user_level == user_level)
if not fetch_all_users:
filters.append(UserProfile.receive_email_alerts == receive_email_alerts)

# Query the database with the constructed filters
users = UserProfile.query.filter(*filters).all()
query = query.filter(UserProfile.receive_email_alerts == receive_email_alerts)

# Check if no users were found
if not users:
return None
# Fetch users based on the query
users = query.all()

# Return list of email addresses
return [user.email for user in users]
# Return list of email addresses or None if no users found
return [user.email for user in users] if users else None
Loading