Initial commit — Stupid Simple Network Inventory
Application web d'inventaire réseau manuel avec FastAPI, Vue 3 et Docker. Inclut l'authentification JWT, la découverte ICMP, et la topologie en cards CSS. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import ipaddress
|
||||
import re
|
||||
from typing import Optional, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, field_validator
|
||||
from sqlalchemy import nullsfirst
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database import get_db
|
||||
import models
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
_COLOR_RE = re.compile(r"^#[0-9a-fA-F]{6}$")
|
||||
|
||||
|
||||
class VlanCreate(BaseModel):
|
||||
vlan_id: Optional[int] = None
|
||||
name: str
|
||||
cidr: Optional[str] = ""
|
||||
color: str = "#4A90D9"
|
||||
|
||||
@field_validator("vlan_id")
|
||||
@classmethod
|
||||
def _vlan_id(cls, v: Optional[int]) -> Optional[int]:
|
||||
if v is not None and not (1 <= v <= 4094):
|
||||
raise ValueError("vlan_id must be between 1 and 4094")
|
||||
return v
|
||||
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
def _name(cls, v: str) -> str:
|
||||
v = v.strip()
|
||||
if not v:
|
||||
raise ValueError("name cannot be empty")
|
||||
if len(v) > 100:
|
||||
raise ValueError("name too long (max 100 characters)")
|
||||
return v
|
||||
|
||||
@field_validator("cidr")
|
||||
@classmethod
|
||||
def _cidr(cls, v: Optional[str]) -> Optional[str]:
|
||||
if v:
|
||||
try:
|
||||
ipaddress.ip_network(v, strict=False)
|
||||
except ValueError:
|
||||
raise ValueError(f"Invalid CIDR notation: {v!r}")
|
||||
return v
|
||||
|
||||
@field_validator("color")
|
||||
@classmethod
|
||||
def _color(cls, v: str) -> str:
|
||||
if not _COLOR_RE.match(v):
|
||||
raise ValueError("color must be a 6-digit hex color (e.g. #4A90D9)")
|
||||
return v
|
||||
|
||||
|
||||
class VlanOut(VlanCreate):
|
||||
id: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
@router.get("/", response_model=List[VlanOut])
|
||||
def list_vlans(db: Session = Depends(get_db)):
|
||||
return db.query(models.Vlan).order_by(nullsfirst(models.Vlan.vlan_id)).all()
|
||||
|
||||
|
||||
@router.post("/", response_model=VlanOut)
|
||||
def create_vlan(vlan: VlanCreate, db: Session = Depends(get_db)):
|
||||
if vlan.vlan_id is not None:
|
||||
existing = db.query(models.Vlan).filter(models.Vlan.vlan_id == vlan.vlan_id).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"VLAN {vlan.vlan_id} existe déjà")
|
||||
db_vlan = models.Vlan(**vlan.model_dump())
|
||||
db.add(db_vlan)
|
||||
db.commit()
|
||||
db.refresh(db_vlan)
|
||||
return db_vlan
|
||||
|
||||
|
||||
@router.put("/{vlan_pk}", response_model=VlanOut)
|
||||
def update_vlan(vlan_pk: int, vlan: VlanCreate, db: Session = Depends(get_db)):
|
||||
db_vlan = db.query(models.Vlan).filter(models.Vlan.id == vlan_pk).first()
|
||||
if not db_vlan:
|
||||
raise HTTPException(status_code=404, detail="VLAN introuvable")
|
||||
for k, v in vlan.model_dump().items():
|
||||
setattr(db_vlan, k, v)
|
||||
db.commit()
|
||||
db.refresh(db_vlan)
|
||||
return db_vlan
|
||||
|
||||
|
||||
@router.delete("/{vlan_pk}")
|
||||
def delete_vlan(vlan_pk: int, db: Session = Depends(get_db)):
|
||||
db_vlan = db.query(models.Vlan).filter(models.Vlan.id == vlan_pk).first()
|
||||
if not db_vlan:
|
||||
raise HTTPException(status_code=404, detail="VLAN introuvable")
|
||||
db.delete(db_vlan)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
Reference in New Issue
Block a user