From fc3f17925022ee9c166641359f35e9f04a363290 Mon Sep 17 00:00:00 2001 From: buchtioof <48603083+buchtioof@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:55:36 +0100 Subject: [PATCH] update grabber (sql+req) --- app.py | 84 +++++++++++++++--------- logo.png => assets/logo.png | Bin grabber.py | 125 ++++++++++++++++++++++++++---------- grabber.sh | 61 ++---------------- grabberman.db | Bin 12288 -> 0 bytes readme.md | 2 +- requirements.txt | 3 +- templates/item.html | 104 ++++++++++++++++++++---------- 8 files changed, 225 insertions(+), 154 deletions(-) rename logo.png => assets/logo.png (100%) delete mode 100644 grabberman.db diff --git a/app.py b/app.py index 3d55b50..a88ef79 100644 --- a/app.py +++ b/app.py @@ -3,50 +3,76 @@ from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates import json -from grabber import Grabber +from contextlib import asynccontextmanager -app = FastAPI() +from grabber import Grabber, flotte, create_db_and_tables + +@asynccontextmanager +async def lifespan(app: FastAPI): + create_db_and_tables() + yield + +app = FastAPI(lifespan=lifespan) app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates") -ordi1 = Grabber() @app.post("/endpoint") async def receive_info(request: Request): - # Lire le body brut body = await request.body() - print(body) - - # Parser le JSON try: data = json.loads(body) except json.JSONDecodeError: raise HTTPException(status_code=400, detail="Invalid JSON") - hw = data["HARDWARE"] - sw = data["SOFTWARE"] + sw = data.get("SOFTWARE", {}) + # On récupère l'adresse MAC, c'est notre nouvel identifiant unique + mac = sw.get("mac_address") + hostname = sw.get("hostname", "Inconnu") - ordi1.motherboard = hw["motherboard"] - ordi1.cpu_model = hw["cpu_model"] - ordi1.cpu_id = hw["cpu_id"] - ordi1.cpu_cores = hw["cpu_cores"] - ordi1.cpu_threads = hw["cpu_threads"] - ordi1.cpu_frequency_min = hw["cpu_frequency_min"] - ordi1.cpu_frequency_cur = hw["cpu_frequency_cur"] - ordi1.cpu_frequency_max = hw["cpu_frequency_max"] + if not mac: + raise HTTPException(status_code=400, detail="Adresse MAC manquante dans le JSON") - ordi1.hostname = sw["hostname"] - ordi1.os = sw["os"] - ordi1.arch = sw["arch"] - ordi1.desktop_env = sw["desktop_env"] - ordi1.window_manager = sw["window_manager"] - ordi1.kernel = sw["kernel"] + # Si la machine n'est pas encore connue par son adresse MAC + if mac not in flotte: + print(f"Nouvelle machine détectée : {hostname} ({mac})") + flotte[mac] = Grabber(mac, hostname) + + ordi_actuel = flotte[mac] + ordi_actuel.update(data) + ordi_actuel.save() - print(f"Hostname is {ordi1.hostname}") - print(f"Motherboard serial is {ordi1.motherboard}") + return {"status": "ok", "mac": mac} - return {"status": "ok"} +@app.get("/") +async def list_ordis(request: Request): + """Affiche la liste des ordis, identifiés par MAC mais affichés par Hostname.""" + # On crée des liens qui pointent vers /ordi/ADRESSE_MAC + # Mais pour l'humain, on affiche "Hostname (Mac)" + list_items = [] + for mac, grabber_obj in flotte.items(): + nom_affiche = f"{grabber_obj.hostname} ({mac})" + list_items.append(f'
  • {nom_affiche}
  • ') + + liens_html = "".join(list_items) + return HTMLResponse(f""" + + + Dashboard Grabber + + + +

    Tableau de bord Grabber

    +

    Machines connectées

    + + + + """) -@app.get("/ordi1") -async def show_info(request: Request): - return templates.TemplateResponse("item.html", {"request": request, "ordi": ordi1}) \ No newline at end of file +# L'URL attend maintenant une adresse MAC (ex: /ordi/00:11:22:33:44:55) +@app.get("/ordi/{mac_address}") +async def show_info(request: Request, mac_address: str): + if mac_address in flotte: + return templates.TemplateResponse("item.html", {"request": request, "ordi": flotte[mac_address]}) + else: + return HTMLResponse("

    Machine introuvable

    ", status_code=404) \ No newline at end of file diff --git a/logo.png b/assets/logo.png similarity index 100% rename from logo.png rename to assets/logo.png diff --git a/grabber.py b/grabber.py index 960618d..a3fbd14 100644 --- a/grabber.py +++ b/grabber.py @@ -1,36 +1,95 @@ -#!/usr/bin/python3 -import configparser -import requests +from typing import Optional +from datetime import datetime +from sqlmodel import Field, Session, SQLModel, create_engine -class Grabber(): - motherboard = " " - cpu_model = " " - cpu_id = " " - cpu_cores = " " - cpu_threads = " " - cpu_frequency_min = " " - cpu_frequency_cur = " " - cpu_frequency_max = " " - ram_size = " " - ram_slots = " " - ram_number = " " +# --- CONFIGURATION BASE DE DONNÉES --- +DB_FILE = "grabberman.db" +sqlite_url = f"sqlite:///{DB_FILE}" +engine = create_engine(sqlite_url, echo=False) - hostname = " " - os = " " - arch = " " - desktop_env = " " - window_manager = " " - kernel = " " +# --- MODÈLE DE DONNÉES (TABLE SQL) --- +class SystemLog(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + date_scan: datetime = Field(default_factory=datetime.now) + + # NOUVEAU : mac_address devient un champ indexé important + mac_address: str = Field(index=True) + hostname: str - def fetch_summary(self): - return - def shutdown(): - return - def status(self): - return - def link_to_user(self,user): - return - def remove_user_access(self): - return - def show_users(self): - return + # Champs Hardware + motherboard: Optional[str] = None + cpu_model: Optional[str] = None + cpu_id: Optional[str] = None + cpu_cores: Optional[str] = None + cpu_threads: Optional[str] = None + cpu_frequency_min: Optional[str] = None + cpu_frequency_cur: Optional[str] = None + cpu_frequency_max: Optional[str] = None + gpu_model: Optional[str] = None + ram_slots: Optional[str] = None + + # Champs Software + os: Optional[str] = None + arch: Optional[str] = None + desktop_env: Optional[str] = None + window_manager: Optional[str] = None + kernel: Optional[str] = None + +def create_db_and_tables(): + SQLModel.metadata.create_all(engine) + +# --- GESTION DE LA FLOTTE EN MÉMOIRE --- +# Le dictionnaire stockera maintenant : {"AA:BB:CC:DD:EE:FF": GrabberObject} +flotte = {} + +class Grabber: + def __init__(self, mac_address, hostname="Inconnu"): + self.mac_address = mac_address + self.hostname = hostname + self.data_cache = {} + + def update(self, json_data): + """Met à jour les données et prépare l'objet SQLModel.""" + hw = json_data.get("HARDWARE", {}) + sw = json_data.get("SOFTWARE", {}) + + # Mise à jour du hostname s'il a changé, mais on garde la MAC comme ancre + if "hostname" in sw: + self.hostname = sw["hostname"] + + # On prépare les données pour la DB + self.data_cache = { + "mac_address": self.mac_address, # On n'oublie pas la MAC + "hostname": self.hostname, + "motherboard": hw.get("motherboard", "N/A"), + "cpu_model": hw.get("cpu_model", "N/A"), + "cpu_id": hw.get("cpu_id", "N/A"), + "cpu_cores": hw.get("cpu_cores", "N/A"), + "cpu_threads": hw.get("cpu_threads", "N/A"), + "cpu_frequency_min": hw.get("cpu_frequency_min", "N/A"), + "cpu_frequency_cur": hw.get("cpu_frequency_cur", "N/A"), + "cpu_frequency_max": hw.get("cpu_frequency_max", "N/A"), + "gpu_model": hw.get("gpu_model", "N/A"), + "ram_slots": hw.get("ram_slots", "N/A"), + "os": sw.get("os", "N/A"), + "arch": sw.get("arch", "N/A"), + "desktop_env": sw.get("desktop_env", "N/A"), + "window_manager": sw.get("window_manager", "N/A"), + "kernel": sw.get("kernel", "N/A") + } + + def save(self): + """Enregistre les données via SQLModel.""" + try: + log_entry = SystemLog(**self.data_cache) + with Session(engine) as session: + session.add(log_entry) + session.commit() + session.refresh(log_entry) + print(f"Sauvegarde réussie pour {self.hostname} ({self.mac_address})") + except Exception as e: + print(f"Erreur SQLModel : {e}") + + # Permet d'accéder aux propriétés comme ordi.cpu_model dans le template + def __getattr__(self, name): + return self.data_cache.get(name, "N/A") \ No newline at end of file diff --git a/grabber.sh b/grabber.sh index 8827bed..99fb184 100755 --- a/grabber.sh +++ b/grabber.sh @@ -40,7 +40,7 @@ echo "" #----- Verify dependecies available ----- REQUIRED_CMDS_SIMPLE=(inxi dmidecode lscpu lsblk nproc numfmt) -REQUIRED_CMDS_FULL=(inxi dmidecode lscpu lsblk nproc numfmt python3 jq) +REQUIRED_CMDS_FULL=(inxi dmidecode lscpu lsblk nproc numfmt python3 jq sqlite3) requirements_simple() { echo -n "Checking dependencies... " @@ -184,48 +184,6 @@ done TOTAL_STORAGE=$(numfmt --to iec $TOTAL_STORAGE) #--------------------------------- -# Compile Hardware informations -hardware() { - echo "[HARDWARE]" >> $SUM_FILE - echo "MB_SERIAL = $MB_SERIAL" >> $SUM_FILE - echo "" >> $SUM_FILE - - echo "--- CPU DATA ---" >> $SUM_FILE - echo "CPU_MODEL = $CPU_MODEL" >> $SUM_FILE - echo "CPU_ID = $CPU_ID" >> $SUM_FILE - echo "CPU_CORES=$CPU_CORES" >> $SUM_FILE - echo "CPU_THREADS=$CPU_THREADS" >> $SUM_FILE - echo "CPU_FREQUENCY_MIN=$CPU_FREQUENCY_MIN" >> $SUM_FILE - echo "CPU_FREQUENCY_CUR=$CPU_FREQUENCY_CUR" >> $SUM_FILE - echo "CPU_FREQUENCY_MAX=$CPU_FREQUENCY_MAX" >> $SUM_FILE - echo "" >> $SUM_FILE - - echo "--- GPU DATA ---" >> $SUM_FILE - echo "GPU_MODEL=$GPU_MODEL" >> $SUM_FILE - echo "" >> $SUM_FILE - - echo "--- RAM DATA ---" >> $SUM_FILE - echo "RAM_SIZE = $RAM_SIZE" >> $SUM_FILE - echo "RAM_SLOTS=$RAM_SLOTS" >> $SUM_FILE - echo "RAM_NUMBER=$RAM_NUMBER" >> $SUM_FILE - - for i in $(seq 1 $RAM_SLOTS_NUMBER); do - R_SIZE=$(sudo dmidecode --type=memory | grep "Size:" | grep -v "Volatile" | grep -v "Cache" | grep -v "Logical" | cut -d: -f2 | sed -n "${i}p" | sed 's/\ //') - R_SLOT=$i - R_FREQ=$(sudo dmidecode --type=memory | grep Speed | grep -v "Memory" | cut -d: -f2 | sed -n "${i}p" | sed 's/\ //') - - echo "RAM_${i}_SIZE=$R_SIZE" >> $SUM_FILE - echo "RAM_${i}_SLOT=$R_SLOT" >> $SUM_FILE - echo "RAM_${i}_FREQ=$R_FREQ" >> $SUM_FILE - done - echo "" >> $SUM_FILE - - echo "--- STORAGE DATA ---" >> $SUM_FILE - disks_partitions - echo "STORAGE = $TOTAL_STORAGE" >> $SUM_FILE - echo "" >> $SUM_FILE -} - ################################################ ######## SOFTWARE PART ######################### @@ -233,17 +191,7 @@ OS=$(lsb_release -a | grep Description | cut -f2) ARCH=$(uname -a | cut -d' ' -f10) KERNEL=$(uname -r) HOSTNAME=$(hostname) - -# Compile Software informations -software() { - echo "[SOFTWARE]" - echo "OS = $OS" - echo "HOSTNAME = $HOSTNAME" - echo "ARCHITECTURE = $ARCH" - echo "KERNEL = $KERNEL" - echo "DESKTOP_ENV = $XDG_CURRENT_DESKTOP" - echo "WINDOW_MANAGER = $XDG_SESSION_TYPE" -} >> $SUM_FILE +MAC_ADDRESS=$(cat /sys/class/net/$(ls /sys/class/net | grep -vE '^(lo|docker|veth|br)' | head -n 1)/address) ############################################### @@ -261,6 +209,7 @@ json_file() { --arg gpu_model "$GPU_MODEL" \ --arg ram_slots "$RAM_SLOTS" \ --arg hostname "$HOSTNAME" \ + --arg mac_address "$MAC_ADDRESS" \ --arg os "$OS" \ --arg arch "$ARCH" \ --arg desktop_env "$XDG_CURRENT_DESKTOP" \ @@ -281,6 +230,7 @@ json_file() { }, SOFTWARE: { hostname: $hostname, + mac_address:$mac_address, os: $os, arch: $arch, desktop_env: $desktop_env, @@ -311,10 +261,7 @@ python_venv() { echo "It's grabbin time!" hello echo "Fetching hardware data..." -hardware echo "Fetching software data..." -software -echo "Writing everything in summary.txt" if [ "$choice" = "1" ]; then echo "Grabber has complete his mission! Find every logs saved in your home repository inside the /grabber folder." echo "See you space cowboy..." diff --git a/grabberman.db b/grabberman.db deleted file mode 100644 index 9fc86f9d166c9d8b7245f3125a6f1bddbeb465bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI#K}*9h6bJC66FtfFH1PDcQ_G47;@vod3%2QusnC>&QBvP9w?doEwZAjB)({^8^2Vo{tVf>4;fOKL>r%@DAoX>x*1YK|LntlWX zAOHafKmY;|fB*y_009U<00KKKfc}4ne=e>D0SG_<0uX=z1Rwwb2tWV=5cn4O1c|Cl A?f?J) diff --git a/readme.md b/readme.md index fa895fd..9b60c63 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -![grabber logo](./logo.png) +![grabber logo](./assets/logo.png) # Grabber - Fetch all your PC diff --git a/requirements.txt b/requirements.txt index 3ae2b65..d1ed23f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,4 +39,5 @@ starlette==0.50.0 typing-inspection==0.4.2 typing_extensions==4.15.0 urllib3==2.6.3 -uvicorn==0.40.0 \ No newline at end of file +uvicorn==0.40.0 +sqlmodel==0.0.32 \ No newline at end of file diff --git a/templates/item.html b/templates/item.html index c56ca9f..b3fa927 100644 --- a/templates/item.html +++ b/templates/item.html @@ -5,45 +5,83 @@ Informations Système -

    Informations de l'Ordinateur

    +

    Parc Informatique ({{ ordinateurs|length }} machines)

    -
    -

    [HARDWARE]

    - - - - - - - - - - - -
    PropriétéValeur
    Hostname{{ ordi.hostname }}
    CPU{{ ordi.cpu_model }}
    ID CPU{{ ordi.cpu_id }}
    Nombre de Cœurs CPU{{ ordi.cpu_cores }}
    Nombre de Threads CPU{{ ordi.cpu_threads }}
    Fréquence Min CPU{{ ordi.cpu_frequency_min }}
    Fréquence Courante CPU{{ ordi.cpu_frequency_cur }}
    Fréquence Max CPU{{ ordi.cpu_frequency_max }}
    Nombre de slots de RAM{{ ordi.ram_slots }}
    -
    + {% for ordi in ordinateurs %} + +
    +
    +

    🖥️ {{ ordi.hostname }} (MAC: {{ ordi.mac_address }})

    +
    -
    -

    [SOFTWARE]

    - - - - - - - - -
    PropriétéValeur
    Hostname{{ ordi.hostname }}
    OS{{ ordi.os }}
    Architecture{{ ordi.arch }}
    Desktop{{ ordi.desktop_env }}
    Window Manager{{ ordi.window_manager }}
    Kernel{{ ordi.kernel }}
    +
    +

    [HARDWARE]

    + + + + + + + + + + + + + + + +
    PropriétéValeur
    Série Carte Mère{{ ordi.mb_serial }}
    Série Châssis{{ ordi.chassis_serial }}
    CPU{{ ordi.cpu }}
    CPU ID{{ ordi.cpu_id }}
    Nombre de Cœurs CPU{{ ordi.cpu_cores_number }}
    Nombre de Threads CPU{{ ordi.cpu_threads_number }}
    Fréquence Min CPU{{ ordi.cpu_frequency_min }}
    Fréquence Courante CPU{{ ordi.cpu_frequency_cur }}
    Fréquence Max CPU{{ ordi.cpu_frequency_max }}
    Modèle GPU{{ ordi.gpu_model }}
    Stockage Total{{ ordi.stockage_total }}
    Nombre de slots de RAM{{ ordi.ram_slots_number }}
    Génération RAM{{ ordi.ram_gen }}
    +
    + +
    +

    [SOFTWARE]

    + + + + + + + + +
    PropriétéValeur
    Hostname{{ ordi.hostname }}
    OS{{ ordi.os }}
    Architecture{{ ordi.arch }}
    Desktop{{ ordi.desktop }}
    Window Manager{{ ordi.wm }}
    Kernel{{ ordi.kernel }}
    +
    + + {% endfor %} + {% if not ordinateurs %} +

    Aucun ordinateur enregistré dans la base de données.

    + {% endif %} + - + \ No newline at end of file