Setup simple Flask App to monitor servers

For this project I am using an Ubuntu 24.04 server.

Creating the app

Prepare server

First we will need to prepare the server, for the app development

First we Update and Upgrade out Ubuntu server

sudo apt update && sudo apt upgrade

When the update is finished, you’ll need to install Python, and a few extra packages

sudo apt update && sudo apt install -y python3 python3-pip

You can check the versions of Python and Pip like this:

python3 --version
pip --version

Creating our project directory

First we will create the directory for our project

mkdir flask_project
cd flask_project

After that’s done, we want to create a virtual environment. It’s always recommended to create a virtual environment when working with Python. It helps to avoid any conflicts and makes it easy to share the project with all required dependencies.

python3 -m venv venv # Creates the virtual environment
source venv/bin/activate  # Activates the virtual environment (venv)

Installing modules

For the project I am making, I need to install “Flask”, “Flask SQLAlchemy” and “ping3” – you might only need “Flask”, if you are not doing the same project as me.

pip install flask flask_sqlalchemy ping3

You can check the modules you have installed by running:

pip list

Creating the Python code

First, start by creating a file named “app.py” – this is where we will write our code.
Make sure that you are in the right folder (the one from earlier “flask_project”)

nano app.py

Insert the code (don’t mind the danish comments, as this is used for my school project also, I will change it later)

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from ping3 import ping
import os # Bruges til at arbejde med file paths

# Initialiserer Flask Applikationen
app = Flask(__name__)

# Først bliver der defineret hvor SQL-Lite databasen skal gemmes
db_path = os.path.join(os.getcwd(), "serverips.db")
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{db_path}"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

# Initialiserer databasen
db = SQLAlchemy(app)

# Opretter en table i databasen
class Server(db.Model):
    # Laver ID, Name og en kolonne til IP adresser
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    ip = db.Column(db.String(100), unique=True, nullable=False)

# Opretter databasen, hvis den ikke findes i forvejen
with app.app_context():
    db.create_all()

# Definere en URL-sti for hjemmesiden
@app.route("/")
def home():
    # Henter alle servere fra databasen
    servers = Server.query.all()
    # Laver en tom liste til at holde server status
    server_status = []
    # For hver server, bliver der lavet en dictionary og det bliver derefter tilføjet til listen
    for server in servers:
        server_dict = {
            "id": server.id,
            "name": server.name,
            "ip": server.ip,
            "status": check_server_status(server.ip)
        }
        server_status.append(server_dict)
    # Sender de hentede servere til HTML og viser HTML siden
    return render_template("index.html", servers=server_status)

@app.route("/add", methods=["POST"])
def add_server():

    # Henter data fra formular
    name = request.form["name"]
    ip = request.form["ip"]

    # Opretter ny server
    new_server = Server(name=name, ip=ip)

    # Gemmer serveren i databasen
    db.session.add(new_server)
    db.session.commit()

    # Redirect brugeren til hjemmesiden
    return redirect(url_for("home"))

@app.route("/delete/<int:server_id>", methods=["POST"])
def delete_server(server_id):

    # Finder serveren i databasen
    server = Server.query.get(server_id)

    # Sletter serveren
    if server:
        db.session.delete(server)
        db.session.commit()

    # Redirect brugeren til hjemmesiden
    return redirect(url_for("home"))

def check_server_status(ip):
    # Prøver at ping en server, og retunere den status fra ping
    try:
        # Pinger serveren med timeout på 1 sekund
        response = ping(ip, timeout=1)
        return "ONLINE" if response else "OFFLINE"
    except Exception:
        # Hvis ping fejler, bliver den returneret som offline
        return "OFFLINE"

# Hvis filen køres direkte, så eksekveres koden herunder
if __name__ == "__main__":
    # Kører Flask serveren i debug mode, den acceptere forbindelser på alle IP-adresser, og lytter på port 5000
    app.run(debug=True, host="0.0.0.0", port=5000)

Creating the HTML page

In the code above, I’m referring to an HTML document in the folder “templates”. We need to first create this folder, and create the HMTL document inside.

First create the folder “templates”

mkdir templates

Now create and edit the a file named “index.html”

nano index.html

I made some HTML code to display with the APP

<!DOCTYPE html>
<html lang="da">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Server Status</title>
    <style>
        .online { color: green; font-weight: bold; }
        .offline { color: red; font-weight: bold; }
    </style>
    <script>
        function updateStatus() {
            fetch('/status')
            .then(response => response.json())
            .then(data => {
                data.forEach(server => {
                    let row = document.getElementById("server-" + server.id);
                    if (row) {
                        let statusCell = row.querySelector(".status");
                        statusCell.innerText = server.status;
                        statusCell.className = "status " + (server.status === "ONLINE" ? "online" : "offline");
                    }
                });
            })
            .catch(error => console.error('Fejl ved opdatering:', error));
        }

        setInterval(updateStatus, 10000); // Opdatering hvert 10. sekund
    </script>
</head>
<body>
    <h1>Server Status</h1>
    <table border="1">
        <tr>
            <th>Servernavn</th>
            <th>IP-adresse</th>
            <th>Status</th>
            <th>Handling</th>
        </tr>
        {% for server in servers %}
        <tr id="server-{{ server.id }}">
            <td>{{ server.name }}</td>
            <td>{{ server.ip }}</td>
            <td class="status {{ 'online' if server.status == 'ONLINE' else 'offline' }}">
                {{ server.status }}
            </td>
            <td>
                <form action="/delete/{{ server.id }}" method="POST" style="display:inline;">
                    <button type="submit">Slet</button>
                </form>
            </td>
        </tr>
        {% endfor %}
    </table>

    <h2>Tilføj en ny server</h2>
    <form action="/add" method="POST">
        <label for="name">Servernavn:</label>
        <input type="text" id="name" name="name" required>
        <br><br>

        <label for="ip">IP-adresse:</label>
        <input type="text" id="ip" name="ip" required>
        <br><br>

        <button type="submit">Tilføj server</button>
    </form>
</body>
</html>

Testing the APP

To test the app after you have added all the code, simply run the following code, make sure, that you are in the right directory first.

python3 app.py

Go to http://serverip:5000 – you should see the HTML page and be able to add and remove servers you want to monitor. There will also be and “ONLINE” or “OFFLINE” status on all the servers.

Building the APP as a Docker Container

You can create this app as a Docker Container in a few easy steps

Installing Docker

Run the following script, make sure to have curl installed first

curl -fsSL https://get.docker.com | sudo bash

Create a Dockerfile

Make sure you are still in the project directory

nano Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

Create the requirements.txt file

nano requirements.txt
flask
flask_sqlalchemy
ping3

Create docker-compose.yml file

nano docker-compose.yml
version: '3.8'

services:
  flask-app:
    build: .
    container_name: flask-server
    restart: always
    volumes:
      - sqlite_data:/app
    ports:
      - "5000:5000"

volumes:
  sqlite_data:

Build and run the Container

docker build -t flask-server .
docker run -d -p 5000:5000 --name flask-app flask-server

Pushing Container to Docker Hub

docker build -t <your_username>/flask-server:latest .
docker login -u <your_username>
docker push <your_username>/flask-server:latest

Pulling and running the Image on another server

docker pull <your_username>/flask-server:latest
docker run -d -p 5000:5000 --name flask-app <your_username>/flask-server:latest

Leave a Reply

Your email address will not be published. Required fields are marked *