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.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)
print(f"Hostname is {ordi1.hostname}")
print(f"Motherboard serial is {ordi1.motherboard}")
ordi_actuel = flotte[mac]
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")
async def show_info(request: Request):
return templates.TemplateResponse("item.html", {"request": request, "ordi": ordi1})
liens_html = "".join(list_items)
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
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)
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
# NOUVEAU : mac_address devient un champ indexé important
mac_address: str = Field(index=True)
hostname: str
# 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")

View File

@ -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..."

Binary file not shown.

View File

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

View File

@ -40,3 +40,4 @@ typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.6.3
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">
<title>Informations Système</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f9f9f9; }
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 h2 { margin-top: 0; }
table { width: 100%; border-collapse: collapse; }
.section h2 { margin-top: 0; color: #555; border-bottom: 2px solid #ddd; padding-bottom: 5px;}
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
th { background-color: #f2f2f2; width: 30%; }
</style>
</head>
<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">
<h2>[HARDWARE]</h2>
<table>
<tr><th>Propriété</th><th>Valeur</th></tr>
<tr><td>Hostname</td><td>{{ ordi.hostname }}</td></tr>
<tr><td>CPU</td><td>{{ ordi.cpu_model }}</td></tr>
<tr><td>ID CPU</td><td>{{ ordi.cpu_id }}</td></tr>
<tr><td>Nombre de Cœurs CPU</td><td>{{ ordi.cpu_cores }}</td></tr>
<tr><td>Nombre de Threads CPU</td><td>{{ ordi.cpu_threads }}</td></tr>
<tr><td>Série Carte Mère</td><td>{{ ordi.mb_serial }}</td></tr>
<tr><td>Série Châssis</td><td>{{ ordi.chassis_serial }}</td></tr>
<tr><td>CPU</td><td>{{ ordi.cpu }}</td></tr>
<tr><td>CPU ID</td><td>{{ ordi.cpu_id }}</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 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>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>
</div>
@ -40,10 +71,17 @@
<tr><td>Hostname</td><td>{{ ordi.hostname }}</td></tr>
<tr><td>OS</td><td>{{ ordi.os }}</td></tr>
<tr><td>Architecture</td><td>{{ ordi.arch }}</td></tr>
<tr><td>Desktop</td><td>{{ ordi.desktop_env }}</td></tr>
<tr><td>Window Manager</td><td>{{ ordi.window_manager }}</td></tr>
<tr><td>Desktop</td><td>{{ ordi.desktop }}</td></tr>
<tr><td>Window Manager</td><td>{{ ordi.wm }}</td></tr>
<tr><td>Kernel</td><td>{{ ordi.kernel }}</td></tr>
</table>
</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>
</html>