From d8028d17cacf00db718f508988cfd348e64d6fd8 Mon Sep 17 00:00:00 2001 From: RamiusLr Date: Mon, 28 Apr 2025 10:01:03 +0200 Subject: [PATCH] First commit --- .gitignore | 3 + Dockerfile | 15 ++++ compose.yaml | 13 +++ pybeemo | 209 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 + 5 files changed, 243 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 compose.yaml create mode 100755 pybeemo create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67b8f11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv/ +*.csv +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..413cb15 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Init image +FROM python:slim +WORKDIR /app + +# Install requirements +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Import app +COPY pybeemo . +RUN chmod +x pybeemo + +# Run it +EXPOSE 8000 +CMD ["./pybeemo"] diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..6e50128 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,13 @@ +--- +services: + pybeemo: + container_name: pybeemo + restart: always + image: ramiuslr/pybeemo:latest # You should set this tag to a fixed version + ports: + - 8000:8000 + environment: + PYBEEMO_USER: + PYBEEMO_PASSWORD: + # PYBEEMO_INTERVAL: optional, defaults to 30 mins +... diff --git a/pybeemo b/pybeemo new file mode 100755 index 0000000..d838ea3 --- /dev/null +++ b/pybeemo @@ -0,0 +1,209 @@ +#!/bin/env python + +import http.server +import io +import os +import socketserver +import sys +import time +from datetime import datetime +from threading import Lock, Thread + +import pandas as pd +from dotenv import load_dotenv +from requests import Session + +# Global variables +data_lock = Lock() +csv_data = {"licenses": None, "backupsets": None, "groups": None} + + +class BeemoHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + with data_lock: + if self.path == "/licenses.csv": + self.serve_csv(csv_data["licenses"]) + elif self.path == "/backupsets.csv": + self.serve_csv(csv_data["backupsets"]) + elif self.path == "/groups.csv": + self.serve_csv(csv_data["groups"]) + else: + self.send_response(404) + self.end_headers() + self.wfile.write(b"File not found") + + def serve_csv(self, data): + if data is None: + self.send_response(503) + self.send_header("Content-type", "text/plain") + self.end_headers() + self.wfile.write(b"Service unavailable - data not yet generated") + return + + self.send_response(200) + self.send_header("Content-type", "text/csv") + self.send_header( + "Content-Disposition", "attachment; filename=" + self.path[1:] + ) + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data.encode("utf-8")) + + +def main(): + # Read parameters from env + load_dotenv() + + username = os.getenv("PYBEEMO_USER") + if not username: + print("ERROR: Please set username") + sys.exit(1) + + password = os.getenv("PYBEEMO_PASSWORD") + if not password: + print("ERROR: Please set a password") + sys.exit(1) + + port = 8000 + + interval = os.getenv("PYBEEMO_INTERVAL") + if not interval: + print("No interval provided, setting default (30)") + interval = 30 + else: + interval = int(interval) + + # Init persistent session + s = Session() + # Login + login(s, username, password) + # Start data update thread + update_thread = Thread(target=update_data, args=(s, interval), daemon=True) + update_thread.start() + + # Start HTTP server + Handler = BeemoHandler + with socketserver.TCPServer(("", port), Handler) as httpd: + print(f"[{datetime.now()}] Running at http://localhost:{port}") + print("Available endpoints:") + print(f" http://localhost:{port}/licenses.csv") + print(f" http://localhost:{port}/backupsets.csv") + print(f" http://localhost:{port}/groups.csv") + print(f"Data refresh interval: {interval} minutes") + httpd.serve_forever() + + +def login(s: Session, username: str, password: str) -> bool: + url = "https://client.beemotechnologie.com/login_validate.php" + data = { + "login": username, + "mdp": password, + "type": "login", + } + r = s.post(url, data=data) + + # Handle failed login checking response url + if r.url == "https://client.beemotechnologie.com/login.php": + print("Login failure, please check username and password") + sys.exit(1) + + +# Functions used to format retrieved data +def get_licenses(s: Session): + url = "https://client.beemotechnologie.com/logic/export_licence.php" + r = io.BytesIO(s.get(url).content) + cols = [ + "License", + "Client", + "Storage (GB)", + "Remote Storage Quota (GB)", + "Disk Usage", + "Remote status", + ] + df = pd.read_csv(r, usecols=cols, sep=";", encoding="ISO-8859-1") + mapping = { + "License": "Numéro de licence", + "Client": "Client", + "Storage (GB)": "Volume externalisé", + "Remote Storage Quota (GB)": "Quota", + "Disk Usage": "Utilisation du disque local", + "Remote status": "Etat de l'externalisation", + } + df.rename(columns=mapping, inplace=True) + df["Utilisation du disque local"] = df[ + "Utilisation du disque local" + ].str.replace("Unknown", "0") + df["Utilisation du disque local"] = df[ + "Utilisation du disque local" + ].str.replace(" %", "") + df["Utilisation du disque local"] = ( + df["Utilisation du disque local"].str.replace(",", ".").astype(float) + ) + df["Etat de l'externalisation"] = df[ + "Etat de l'externalisation" + ].str.replace(" %", "") + df["Ratio"] = df["Volume externalisé"] / df["Quota"] * 100 + df["Ratio"] = df["Ratio"].round(1) + return df.to_csv(index=False) + + +def get_backupsets(s: Session): + url = "https://client.beemotechnologie.com/logic/export_jds.php" + r = io.BytesIO(s.get(url).content) + cols = ["License", "Client", "Backupsets", "Backup Status"] + df = pd.read_csv(r, usecols=cols, sep=";", encoding="ISO-8859-1") + mapping = { + "License": "Numéro de licence", + "Client": "Client", + "Backupsets": "Jeu de sauvegarde", + "Backup Status": "Etat", + } + df.rename(columns=mapping, inplace=True) + df.query('Etat != "Ok"', inplace=True) + return df.to_csv(index=False) + + +def get_groups(s: Session): + url = "https://client.beemotechnologie.com/logic/export_groupes.php" + r = io.BytesIO(s.get(url).content) + cols = ["Name", "Storage (GB)", "Remote Storage Quota (GB)"] + df = pd.read_csv(r, usecols=cols, sep=";", encoding="ISO-8859-1") + mapping = { + "Name": "Client", + "Storage (GB)": "Utilisé", + "Remote Storage Quota (GB)": "Quota", + } + df.rename(columns=mapping, inplace=True) + df["Utilisé"] = df["Utilisé"].str.replace(",", ".").astype(float) + df["Ratio"] = df["Utilisé"] / df["Quota"] * 100 + df["Ratio"] = df["Ratio"].round(1) + return df.to_csv(index=False) + + +def update_data(s: Session, interval): + while True: + print(f"[{datetime.now()}] Updating data...") + + # Update licenses + data = get_licenses(s) + with data_lock: + csv_data["licenses"] = data + print(f"[{datetime.now()}] Updated licenses.csv in memory") + + # Update licenses + data = get_backupsets(s) + with data_lock: + csv_data["backupsets"] = data + print(f"[{datetime.now()}] Updated backupsets.csv in memory") + + # Update groups + data = get_groups(s) + with data_lock: + csv_data["groups"] = data + print(f"[{datetime.now()}] Updated groups.csv in memory") + + # Wait time interval before updating again + time.sleep(interval * 60) + + +main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..92823b3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pandas +requests +dotenv