235 lines
7.7 KiB
Python
235 lines
7.7 KiB
Python
import os
|
|
from pathlib import Path
|
|
from dotenv import load_dotenv
|
|
from flask import Flask, jsonify, request, render_template, redirect, url_for, flash
|
|
|
|
from classes import Equipment, Interface
|
|
from database import (
|
|
init_database,
|
|
reset_database,
|
|
prompt_reset,
|
|
fetch_all_equipments,
|
|
fetch_equipment_by_id,
|
|
insert_equipment,
|
|
update_equipment,
|
|
delete_equipment,
|
|
)
|
|
|
|
load_dotenv()
|
|
|
|
app = Flask(__name__)
|
|
|
|
def _required_env(key: str) -> str:
|
|
value = os.environ.get(key)
|
|
if not value:
|
|
raise RuntimeError(
|
|
f"Variable d'environnement {key!r} manquante. "
|
|
f"Vérifiez votre fichier .env (voir .env.example)."
|
|
)
|
|
return value
|
|
|
|
app.config["SECRET_KEY"] = _required_env("FLASK_SECRET_KEY")
|
|
app.config["DEBUG"] = os.environ.get("FLASK_DEBUG", "False").lower() in {"1", "true", "yes"}
|
|
app.config["DATABASE_PATH"] = os.environ.get("DATABASE_PATH", "databases/inventory.db")
|
|
|
|
ALLOWED_TYPES = {"Server", "Instance", "Switch", "Gateway", "Router", "WAF", "Database"}
|
|
|
|
def _parse_form_equipment(form) -> tuple[Equipment | None, str | None]:
|
|
hostname = (form.get("hostname") or "").strip()
|
|
if not hostname:
|
|
return None, "Hostname requis."
|
|
|
|
eq_type = form.get("type", "Server")
|
|
if eq_type not in ALLOWED_TYPES:
|
|
return None, f"Type invalide : {eq_type}."
|
|
|
|
ip = (form.get("ip_address") or "").strip() or "255.255.255.0"
|
|
is_active = form.get("is_active") == "on"
|
|
|
|
latency_raw = (form.get("latency") or "").strip()
|
|
latency = None
|
|
if latency_raw:
|
|
try:
|
|
latency = float(latency_raw)
|
|
if latency < 0:
|
|
return None, "Latence doit être positive."
|
|
except ValueError:
|
|
return None, "Latence invalide."
|
|
|
|
ports = form.getlist("ports[]")
|
|
services = form.getlist("services[]")
|
|
interfaces: list[Interface] = []
|
|
for port_raw, service in zip(ports, services):
|
|
if not port_raw:
|
|
continue
|
|
try:
|
|
port = int(port_raw)
|
|
if not (0 <= port <= 65535):
|
|
return None, f"Port invalide : {port_raw}."
|
|
except ValueError:
|
|
return None, f"Port non numérique : {port_raw}."
|
|
if not service.strip():
|
|
return None, "Service requis pour chaque interface."
|
|
interfaces.append(Interface(port=port, service=service.strip()))
|
|
|
|
return Equipment(
|
|
hostname=hostname,
|
|
type=eq_type,
|
|
ip_address=ip,
|
|
is_active=is_active,
|
|
latency=latency,
|
|
interfaces=interfaces,
|
|
), None
|
|
|
|
def _equipment_to_dict(eq: Equipment) -> dict:
|
|
return {
|
|
"id": eq.id,
|
|
"hostname": eq.hostname,
|
|
"type": eq.type,
|
|
"ip_address": eq.ip_address,
|
|
"is_active": eq.is_active,
|
|
"latency": eq.latency,
|
|
"interfaces": [
|
|
{"id": i.id, "port": i.port, "service": i.service}
|
|
for i in eq.interfaces
|
|
],
|
|
}
|
|
|
|
# Partie FRONT
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
|
|
|
@app.route('/equipments', methods=['GET'])
|
|
def equipments_page():
|
|
equipments = fetch_all_equipments()
|
|
return render_template('equipments.html', equipments=equipments, types=sorted(ALLOWED_TYPES))
|
|
|
|
@app.route('/equipments', methods=['POST'])
|
|
def create_equipment_form():
|
|
eq, err = _parse_form_equipment(request.form)
|
|
if err:
|
|
flash(err, "error")
|
|
return redirect(url_for('equipments_page'))
|
|
insert_equipment(eq)
|
|
flash(f"Équipement « {eq.hostname} » créé.", "success")
|
|
return redirect(url_for('equipments_page'))
|
|
|
|
@app.route('/equipments/<int:equipment_id>/toggle', methods=['POST'])
|
|
def toggle_equipment(equipment_id: int):
|
|
eq = fetch_equipment_by_id(equipment_id)
|
|
if eq is None:
|
|
flash(f"Équipement {equipment_id} introuvable.", "error")
|
|
return redirect(url_for('equipments_page'))
|
|
eq.is_active = not eq.is_active
|
|
update_equipment(eq)
|
|
flash(f"État de « {eq.hostname} » mis à jour.", "success")
|
|
return redirect(url_for('equipments_page'))
|
|
|
|
@app.route('/equipments/<int:equipment_id>/delete', methods=['POST'])
|
|
def delete_equipment_form(equipment_id: int):
|
|
eq = fetch_equipment_by_id(equipment_id)
|
|
if eq is None:
|
|
flash(f"Équipement {equipment_id} introuvable.", "error")
|
|
return redirect(url_for('equipments_page'))
|
|
delete_equipment(equipment_id)
|
|
flash(f"Équipement « {eq.hostname} » supprimé.", "success")
|
|
return redirect(url_for('equipments_page'))
|
|
|
|
# Partie API
|
|
|
|
@app.get("/api/equipments")
|
|
def list_equipments_api():
|
|
return jsonify([_equipment_to_dict(eq) for eq in fetch_all_equipments()])
|
|
|
|
@app.get("/api/equipments/<int:equipment_id>")
|
|
def get_equipment_api(equipment_id: int):
|
|
eq = fetch_equipment_by_id(equipment_id)
|
|
if eq is None:
|
|
return jsonify({"error": "Équipement introuvable."}), 404
|
|
return jsonify(_equipment_to_dict(eq))
|
|
|
|
@app.post("/api/equipments")
|
|
def create_equipment_api():
|
|
data = request.get_json(silent=True) or {}
|
|
hostname = (data.get("hostname") or "").strip()
|
|
if not hostname:
|
|
return jsonify({"error": "Hostname requis."}), 400
|
|
eq_type = data.get("type", "Server")
|
|
if eq_type not in ALLOWED_TYPES:
|
|
return jsonify({"error": f"Type invalide : {eq_type}."}), 400
|
|
eq = Equipment(
|
|
hostname=hostname,
|
|
type=eq_type,
|
|
ip_address=(data.get("ip_address") or "255.255.255.0").strip(),
|
|
is_active=bool(data.get("is_active", True)),
|
|
latency=data.get("latency"),
|
|
interfaces=[],
|
|
)
|
|
new_id = insert_equipment(eq)
|
|
eq.id = new_id
|
|
return jsonify(_equipment_to_dict(eq)), 201
|
|
|
|
@app.put("/api/equipments/<int:equipment_id>")
|
|
def update_equipment_api(equipment_id: int):
|
|
eq = fetch_equipment_by_id(equipment_id)
|
|
if eq is None:
|
|
return jsonify({"error": f"Équipement {equipment_id} introuvable."}), 404
|
|
|
|
data = request.get_json(silent=True) or {}
|
|
hostname = (data.get("hostname") or "").strip()
|
|
if not hostname:
|
|
return jsonify({"error": "Hostname requis."}), 400
|
|
|
|
eq_type = data.get("type", "Server")
|
|
if eq_type not in ALLOWED_TYPES:
|
|
return jsonify({"error": f"Type invalide : {eq_type}."}), 400
|
|
|
|
latency = data.get("latency")
|
|
if latency is not None:
|
|
try:
|
|
latency = float(latency)
|
|
if latency < 0:
|
|
return jsonify({"error": "Latence doit être positive."}), 400
|
|
except (ValueError, TypeError):
|
|
return jsonify({"error": "Latence invalide."}), 400
|
|
|
|
eq.hostname = hostname
|
|
eq.type = eq_type
|
|
eq.ip_address = (data.get("ip_address") or "255.255.255.0").strip()
|
|
eq.is_active = bool(data.get("is_active", True))
|
|
eq.latency = latency
|
|
|
|
update_equipment(eq)
|
|
return jsonify(_equipment_to_dict(eq)), 200
|
|
|
|
@app.patch("/api/equipments/<int:equipment_id>/toggle")
|
|
def toggle_equipment_api(equipment_id: int):
|
|
eq = fetch_equipment_by_id(equipment_id)
|
|
if eq is None:
|
|
return jsonify({"error": f"Équipement {equipment_id} introuvable."}), 404
|
|
eq.is_active = not eq.is_active
|
|
update_equipment(eq)
|
|
return jsonify(_equipment_to_dict(eq)), 200
|
|
|
|
@app.delete("/api/equipments/<int:equipment_id>")
|
|
def delete_equipment_api(equipment_id: int):
|
|
eq = fetch_equipment_by_id(equipment_id)
|
|
if eq is None:
|
|
return jsonify({"error": f"Équipement {equipment_id} introuvable."}), 404
|
|
delete_equipment(equipment_id)
|
|
return jsonify({"message": f"Équipement « {eq.hostname} » supprimé."}), 200
|
|
|
|
if __name__ == '__main__':
|
|
host = os.environ.get("FLASK_HOST", "127.0.0.1")
|
|
port = int(os.environ.get("FLASK_PORT", "5000"))
|
|
|
|
if prompt_reset():
|
|
reset_database()
|
|
print("Base réinitialisée.")
|
|
else:
|
|
init_database()
|
|
|
|
app.run(host=host, port=port, debug=app.config["DEBUG"])
|