update grabber (sql+req)

This commit is contained in:
buchtioof 2026-02-10 10:55:36 +01:00
parent 532358e2fc
commit fc3f179250
8 changed files with 225 additions and 154 deletions

84
app.py
View File

@ -3,50 +3,76 @@ from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
import json 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") app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
ordi1 = Grabber()
@app.post("/endpoint") @app.post("/endpoint")
async def receive_info(request: Request): async def receive_info(request: Request):
# Lire le body brut
body = await request.body() body = await request.body()
print(body)
# Parser le JSON
try: try:
data = json.loads(body) data = json.loads(body)
except json.JSONDecodeError: except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON") raise HTTPException(status_code=400, detail="Invalid JSON")
hw = data["HARDWARE"] sw = data.get("SOFTWARE", {})
sw = data["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"] if not mac:
ordi1.cpu_model = hw["cpu_model"] raise HTTPException(status_code=400, detail="Adresse MAC manquante dans le JSON")
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"]
ordi1.hostname = sw["hostname"] # Si la machine n'est pas encore connue par son adresse MAC
ordi1.os = sw["os"] if mac not in flotte:
ordi1.arch = sw["arch"] print(f"Nouvelle machine détectée : {hostname} ({mac})")
ordi1.desktop_env = sw["desktop_env"] flotte[mac] = Grabber(mac, hostname)
ordi1.window_manager = sw["window_manager"]
ordi1.kernel = sw["kernel"]
print(f"Hostname is {ordi1.hostname}") ordi_actuel = flotte[mac]
print(f"Motherboard serial is {ordi1.motherboard}") ordi_actuel.update(data)
ordi_actuel.save()
return {"status": "ok"} return {"status": "ok", "mac": mac}
@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} <small>({mac})</small>"
list_items.append(f'<li><a href="/ordi/{mac}">{nom_affiche}</a></li>')
@app.get("/ordi1") liens_html = "".join(list_items)
async def show_info(request: Request):
return templates.TemplateResponse("item.html", {"request": request, "ordi": ordi1}) return HTMLResponse(f"""
<html>
<head>
<title>Dashboard Grabber</title>
<style>body{{font-family:sans-serif; padding:20px;}} li{{margin:5px 0;}}</style>
</head>
<body>
<h1>Tableau de bord Grabber</h1>
<h2>Machines connectées</h2>
<ul>{liens_html or "En attente de données..."}</ul>
</body>
</html>
""")
# 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("<h1>Machine introuvable</h1>", status_code=404)

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -1,36 +1,95 @@
#!/usr/bin/python3 from typing import Optional
import configparser from datetime import datetime
import requests from sqlmodel import Field, Session, SQLModel, create_engine
class Grabber(): # --- CONFIGURATION BASE DE DONNÉES ---
motherboard = " " DB_FILE = "grabberman.db"
cpu_model = " " sqlite_url = f"sqlite:///{DB_FILE}"
cpu_id = " " engine = create_engine(sqlite_url, echo=False)
cpu_cores = " "
cpu_threads = " "
cpu_frequency_min = " "
cpu_frequency_cur = " "
cpu_frequency_max = " "
ram_size = " "
ram_slots = " "
ram_number = " "
hostname = " " # --- MODÈLE DE DONNÉES (TABLE SQL) ---
os = " " class SystemLog(SQLModel, table=True):
arch = " " id: Optional[int] = Field(default=None, primary_key=True)
desktop_env = " " date_scan: datetime = Field(default_factory=datetime.now)
window_manager = " "
kernel = " "
def fetch_summary(self): # NOUVEAU : mac_address devient un champ indexé important
return mac_address: str = Field(index=True)
def shutdown(): hostname: str
return
def status(self): # Champs Hardware
return motherboard: Optional[str] = None
def link_to_user(self,user): cpu_model: Optional[str] = None
return cpu_id: Optional[str] = None
def remove_user_access(self): cpu_cores: Optional[str] = None
return cpu_threads: Optional[str] = None
def show_users(self): cpu_frequency_min: Optional[str] = None
return 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")

View File

@ -40,7 +40,7 @@ echo ""
#----- Verify dependecies available ----- #----- Verify dependecies available -----
REQUIRED_CMDS_SIMPLE=(inxi dmidecode lscpu lsblk nproc numfmt) 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() { requirements_simple() {
echo -n "Checking dependencies... " echo -n "Checking dependencies... "
@ -184,48 +184,6 @@ done
TOTAL_STORAGE=$(numfmt --to iec $TOTAL_STORAGE) 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 ######################### ######## SOFTWARE PART #########################
@ -233,17 +191,7 @@ OS=$(lsb_release -a | grep Description | cut -f2)
ARCH=$(uname -a | cut -d' ' -f10) ARCH=$(uname -a | cut -d' ' -f10)
KERNEL=$(uname -r) KERNEL=$(uname -r)
HOSTNAME=$(hostname) HOSTNAME=$(hostname)
MAC_ADDRESS=$(cat /sys/class/net/$(ls /sys/class/net | grep -vE '^(lo|docker|veth|br)' | head -n 1)/address)
# 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
############################################### ###############################################
@ -261,6 +209,7 @@ json_file() {
--arg gpu_model "$GPU_MODEL" \ --arg gpu_model "$GPU_MODEL" \
--arg ram_slots "$RAM_SLOTS" \ --arg ram_slots "$RAM_SLOTS" \
--arg hostname "$HOSTNAME" \ --arg hostname "$HOSTNAME" \
--arg mac_address "$MAC_ADDRESS" \
--arg os "$OS" \ --arg os "$OS" \
--arg arch "$ARCH" \ --arg arch "$ARCH" \
--arg desktop_env "$XDG_CURRENT_DESKTOP" \ --arg desktop_env "$XDG_CURRENT_DESKTOP" \
@ -281,6 +230,7 @@ json_file() {
}, },
SOFTWARE: { SOFTWARE: {
hostname: $hostname, hostname: $hostname,
mac_address:$mac_address,
os: $os, os: $os,
arch: $arch, arch: $arch,
desktop_env: $desktop_env, desktop_env: $desktop_env,
@ -311,10 +261,7 @@ python_venv() {
echo "It's grabbin time!" echo "It's grabbin time!"
hello hello
echo "Fetching hardware data..." echo "Fetching hardware data..."
hardware
echo "Fetching software data..." echo "Fetching software data..."
software
echo "Writing everything in summary.txt"
if [ "$choice" = "1" ]; then if [ "$choice" = "1" ]; then
echo "Grabber has complete his mission! Find every logs saved in your home repository inside the /grabber folder." echo "Grabber has complete his mission! Find every logs saved in your home repository inside the /grabber folder."
echo "See you space cowboy..." echo "See you space cowboy..."

Binary file not shown.

View File

@ -1,4 +1,4 @@
![grabber logo](./logo.png) ![grabber logo](./assets/logo.png)
# Grabber - Fetch all your PC # Grabber - Fetch all your PC

View File

@ -40,3 +40,4 @@ typing-inspection==0.4.2
typing_extensions==4.15.0 typing_extensions==4.15.0
urllib3==2.6.3 urllib3==2.6.3
uvicorn==0.40.0 uvicorn==0.40.0
sqlmodel==0.0.32

View File

@ -5,31 +5,62 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Informations Système</title> <title>Informations Système</title>
<style> <style>
body { font-family: Arial, sans-serif; margin: 20px; } body { font-family: Arial, sans-serif; margin: 20px; background-color: #f9f9f9; }
h1 { color: #333; } h1 { color: #333; text-align: center; }
/* Style pour le conteneur d'un seul PC */
.computer-container {
background-color: white;
border: 2px solid #333;
border-radius: 8px;
margin-bottom: 40px; /* Espace entre chaque ordi */
padding: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.computer-header {
background-color: #333;
color: white;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
.section { margin-bottom: 20px; border: 1px solid #ccc; padding: 10px; } .section { margin-bottom: 20px; border: 1px solid #ccc; padding: 10px; }
.section h2 { margin-top: 0; } .section h2 { margin-top: 0; color: #555; border-bottom: 2px solid #ddd; padding-bottom: 5px;}
table { width: 100%; border-collapse: collapse; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; } th { background-color: #f2f2f2; width: 30%; }
</style> </style>
</head> </head>
<body> <body>
<h1>Informations de l'Ordinateur</h1> <h1>Parc Informatique ({{ ordinateurs|length }} machines)</h1>
{% for ordi in ordinateurs %}
<div class="computer-container">
<div class="computer-header">
<h2>🖥️ {{ ordi.hostname }} <small>(MAC: {{ ordi.mac_address }})</small></h2>
</div>
<div class="section"> <div class="section">
<h2>[HARDWARE]</h2> <h2>[HARDWARE]</h2>
<table> <table>
<tr><th>Propriété</th><th>Valeur</th></tr> <tr><th>Propriété</th><th>Valeur</th></tr>
<tr><td>Hostname</td><td>{{ ordi.hostname }}</td></tr> <tr><td>Série Carte Mère</td><td>{{ ordi.mb_serial }}</td></tr>
<tr><td>CPU</td><td>{{ ordi.cpu_model }}</td></tr> <tr><td>Série Châssis</td><td>{{ ordi.chassis_serial }}</td></tr>
<tr><td>ID CPU</td><td>{{ ordi.cpu_id }}</td></tr> <tr><td>CPU</td><td>{{ ordi.cpu }}</td></tr>
<tr><td>Nombre de Cœurs CPU</td><td>{{ ordi.cpu_cores }}</td></tr> <tr><td>CPU ID</td><td>{{ ordi.cpu_id }}</td></tr>
<tr><td>Nombre de Threads CPU</td><td>{{ ordi.cpu_threads }}</td></tr> <tr><td>Nombre de Cœurs CPU</td><td>{{ ordi.cpu_cores_number }}</td></tr>
<tr><td>Nombre de Threads CPU</td><td>{{ ordi.cpu_threads_number }}</td></tr>
<tr><td>Fréquence Min CPU</td><td>{{ ordi.cpu_frequency_min }}</td></tr> <tr><td>Fréquence Min CPU</td><td>{{ ordi.cpu_frequency_min }}</td></tr>
<tr><td>Fréquence Courante CPU</td><td>{{ ordi.cpu_frequency_cur }}</td></tr> <tr><td>Fréquence Courante CPU</td><td>{{ ordi.cpu_frequency_cur }}</td></tr>
<tr><td>Fréquence Max CPU</td><td>{{ ordi.cpu_frequency_max }}</td></tr> <tr><td>Fréquence Max CPU</td><td>{{ ordi.cpu_frequency_max }}</td></tr>
<tr><td>Nombre de slots de RAM</td><td>{{ ordi.ram_slots }}</td></tr> <tr><td>Modèle GPU</td><td>{{ ordi.gpu_model }}</td></tr>
<tr><td>Stockage Total</td><td>{{ ordi.stockage_total }}</td></tr>
<tr><td>Nombre de slots de RAM</td><td>{{ ordi.ram_slots_number }}</td></tr>
<tr><td>Génération RAM</td><td>{{ ordi.ram_gen }}</td></tr>
</table> </table>
</div> </div>
@ -40,10 +71,17 @@
<tr><td>Hostname</td><td>{{ ordi.hostname }}</td></tr> <tr><td>Hostname</td><td>{{ ordi.hostname }}</td></tr>
<tr><td>OS</td><td>{{ ordi.os }}</td></tr> <tr><td>OS</td><td>{{ ordi.os }}</td></tr>
<tr><td>Architecture</td><td>{{ ordi.arch }}</td></tr> <tr><td>Architecture</td><td>{{ ordi.arch }}</td></tr>
<tr><td>Desktop</td><td>{{ ordi.desktop_env }}</td></tr> <tr><td>Desktop</td><td>{{ ordi.desktop }}</td></tr>
<tr><td>Window Manager</td><td>{{ ordi.window_manager }}</td></tr> <tr><td>Window Manager</td><td>{{ ordi.wm }}</td></tr>
<tr><td>Kernel</td><td>{{ ordi.kernel }}</td></tr> <tr><td>Kernel</td><td>{{ ordi.kernel }}</td></tr>
</table> </table>
</div> </div>
</div>
{% endfor %}
{% if not ordinateurs %}
<p style="text-align:center; color: red;">Aucun ordinateur enregistré dans la base de données.</p>
{% endif %}
</body> </body>
</html> </html>