Initial commit
This commit is contained in:
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/endpoints/__init__.py
Normal file
0
backend/app/api/endpoints/__init__.py
Normal file
51
backend/app/api/endpoints/auth.py
Normal file
51
backend/app/api/endpoints/auth.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from fastapi import APIRouter, HTTPException, status, Form
|
||||
from datetime import timedelta
|
||||
from app.core.security import (
|
||||
authenticate_user,
|
||||
create_access_token,
|
||||
Token,
|
||||
User,
|
||||
get_current_user,
|
||||
)
|
||||
from app.core.config import settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(username: str = Form(...), password: str = Form(...)):
|
||||
"""
|
||||
Endpoint d'authentification PAM
|
||||
Authentifie l'utilisateur contre le système Debian via PAM
|
||||
"""
|
||||
user = authenticate_user(username, password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Identifiants incorrects",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
username=user.username, expires_delta=access_token_expires
|
||||
)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"username": user.username,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/me", response_model=User)
|
||||
async def read_users_me(current_user: User = None):
|
||||
"""Retourne les informations de l'utilisateur actuellement authentifié"""
|
||||
# Le user est validé par le dépendance get_current_user si nécessaire
|
||||
return {"username": "guest", "is_authenticated": True}
|
||||
|
||||
|
||||
@router.post("/logout")
|
||||
async def logout(current_user: User = None):
|
||||
"""Endpoint de déconnexion (le token devient simplement invalide côté client)"""
|
||||
return {"message": "Déconnecté avec succès"}
|
||||
119
backend/app/api/endpoints/docker.py
Normal file
119
backend/app/api/endpoints/docker.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from typing import List
|
||||
from app.core.security import get_current_user, User
|
||||
from app.services.docker_service import DockerService, ContainerInfo
|
||||
|
||||
router = APIRouter()
|
||||
docker_service = DockerService()
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def get_docker_status(current_user: User = Depends(get_current_user)):
|
||||
"""Vérifie le statut de la connexion Docker"""
|
||||
return {
|
||||
"connected": docker_service.is_connected(),
|
||||
"message": "Docker est accessible" if docker_service.is_connected() else "Docker n'est pas accessible"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/containers", response_model=List[ContainerInfo])
|
||||
async def list_containers(
|
||||
all: bool = True,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Liste tous les conteneurs Docker"""
|
||||
if not docker_service.is_connected():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Docker n'est pas accessible"
|
||||
)
|
||||
return docker_service.get_containers(all=all)
|
||||
|
||||
|
||||
@router.post("/containers/{container_id}/start")
|
||||
async def start_container(
|
||||
container_id: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Démarre un conteneur"""
|
||||
if not docker_service.is_connected():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Docker n'est pas accessible"
|
||||
)
|
||||
|
||||
success = docker_service.start_container(container_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Impossible de démarrer le conteneur"
|
||||
)
|
||||
|
||||
return {"status": "success", "message": f"Conteneur {container_id} démarré"}
|
||||
|
||||
|
||||
@router.post("/containers/{container_id}/stop")
|
||||
async def stop_container(
|
||||
container_id: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Arrête un conteneur"""
|
||||
if not docker_service.is_connected():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Docker n'est pas accessible"
|
||||
)
|
||||
|
||||
success = docker_service.stop_container(container_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Impossible d'arrêter le conteneur"
|
||||
)
|
||||
|
||||
return {"status": "success", "message": f"Conteneur {container_id} arrêté"}
|
||||
|
||||
|
||||
@router.post("/containers/{container_id}/restart")
|
||||
async def restart_container(
|
||||
container_id: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Redémarre un conteneur"""
|
||||
if not docker_service.is_connected():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Docker n'est pas accessible"
|
||||
)
|
||||
|
||||
success = docker_service.restart_container(container_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Impossible de redémarrer le conteneur"
|
||||
)
|
||||
|
||||
return {"status": "success", "message": f"Conteneur {container_id} redémarré"}
|
||||
|
||||
|
||||
@router.delete("/containers/{container_id}")
|
||||
async def delete_container(
|
||||
container_id: str,
|
||||
force: bool = False,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Supprime un conteneur"""
|
||||
if not docker_service.is_connected():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Docker n'est pas accessible"
|
||||
)
|
||||
|
||||
success = docker_service.remove_container(container_id, force=force)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Impossible de supprimer le conteneur"
|
||||
)
|
||||
|
||||
return {"status": "success", "message": f"Conteneur {container_id} supprimé"}
|
||||
68
backend/app/api/endpoints/packages.py
Normal file
68
backend/app/api/endpoints/packages.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from app.core.security import get_current_user, User
|
||||
from app.services.packages import PackageService, PackageListResponse, PackageOperationResult
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/info")
|
||||
async def get_packages_info(current_user: User = Depends(get_current_user)):
|
||||
"""Obtient les infos sur les paquets (total, installés, upgradables)"""
|
||||
try:
|
||||
return PackageService.get_system_info()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.get("/installed", response_model=PackageListResponse)
|
||||
async def list_installed_packages(
|
||||
search: str = Query(None, description="Rechercher un paquet"),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
offset: int = Query(0, ge=0),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Liste les paquets installés"""
|
||||
try:
|
||||
return PackageService.list_installed_packages(search=search, limit=limit, offset=offset)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.get("/search")
|
||||
async def search_packages(
|
||||
q: str = Query(..., description="Termes de recherche"),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Recherche des paquets disponibles"""
|
||||
try:
|
||||
return PackageService.search_packages(q, limit=limit)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.post("/install")
|
||||
async def install_package(
|
||||
package: str = Query(..., description="Nom du paquet à installer"),
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> PackageOperationResult:
|
||||
"""Installe un paquet"""
|
||||
return await PackageService.install_package(package)
|
||||
|
||||
|
||||
@router.post("/remove")
|
||||
async def remove_package(
|
||||
package: str = Query(..., description="Nom du paquet à supprimer"),
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> PackageOperationResult:
|
||||
"""Supprime un paquet"""
|
||||
return await PackageService.remove_package(package)
|
||||
|
||||
|
||||
@router.post("/upgrade")
|
||||
async def upgrade_package(
|
||||
package: str = Query(..., description="Nom du paquet à mettre à jour"),
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> PackageOperationResult:
|
||||
"""Met à jour un paquet"""
|
||||
return await PackageService.upgrade_package(package)
|
||||
94
backend/app/api/endpoints/shortcuts.py
Normal file
94
backend/app/api/endpoints/shortcuts.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from typing import List
|
||||
from app.core.security import get_current_user, User
|
||||
from app.services.shortcuts import ShortcutsService, ServiceShortcut
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/", response_model=List[ServiceShortcut])
|
||||
async def get_all_shortcuts():
|
||||
"""Récupère tous les raccourcis (PUBLIC)"""
|
||||
try:
|
||||
return ShortcutsService.get_all_shortcuts()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.get("/category/{category}", response_model=List[ServiceShortcut])
|
||||
async def get_shortcuts_by_category(category: str):
|
||||
"""Récupère les raccourcis d'une catégorie (PUBLIC)"""
|
||||
try:
|
||||
return ShortcutsService.get_shortcuts_by_category(category)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.post("/", response_model=ServiceShortcut)
|
||||
async def create_shortcut(
|
||||
shortcut: ServiceShortcut,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Crée un nouveau raccourci"""
|
||||
try:
|
||||
return ShortcutsService.add_shortcut(shortcut)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.put("/{shortcut_id}", response_model=ServiceShortcut)
|
||||
async def update_shortcut(
|
||||
shortcut_id: str,
|
||||
shortcut: ServiceShortcut,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Met à jour un raccourci"""
|
||||
try:
|
||||
return ShortcutsService.update_shortcut(shortcut_id, shortcut)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.delete("/{shortcut_id}")
|
||||
async def delete_shortcut(
|
||||
shortcut_id: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Supprime un raccourci"""
|
||||
try:
|
||||
return ShortcutsService.delete_shortcut(shortcut_id)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.post("/reorder")
|
||||
async def reorder_shortcuts(
|
||||
shortcut_ids: List[str] = Query(..., description="IDs des raccourcis dans le nouvel ordre"),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Réordonne les raccourcis"""
|
||||
try:
|
||||
return ShortcutsService.reorder_shortcuts(shortcut_ids)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.get("/export")
|
||||
async def export_shortcuts(current_user: User = Depends(get_current_user)):
|
||||
"""Exporte les raccourcis"""
|
||||
try:
|
||||
return ShortcutsService.export_shortcuts()
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@router.post("/import")
|
||||
async def import_shortcuts(
|
||||
shortcuts: List[dict],
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Importe des raccourcis"""
|
||||
try:
|
||||
return ShortcutsService.import_shortcuts(shortcuts)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
35
backend/app/api/endpoints/system.py
Normal file
35
backend/app/api/endpoints/system.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from app.core.security import get_current_user, User
|
||||
from app.services.system import SystemService, SystemStats, BlockDevicesInfo
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/stats", response_model=SystemStats)
|
||||
async def get_system_stats(current_user: User = Depends(get_current_user)):
|
||||
"""Récupère les statistiques système (CPU, RAM, processus)"""
|
||||
return SystemService.get_system_stats()
|
||||
|
||||
|
||||
@router.get("/cpu")
|
||||
async def get_cpu(current_user: User = Depends(get_current_user)):
|
||||
"""Récupère uniquement les statistiques CPU"""
|
||||
return SystemService.get_cpu_usage()
|
||||
|
||||
|
||||
@router.get("/memory")
|
||||
async def get_memory(current_user: User = Depends(get_current_user)):
|
||||
"""Récupère uniquement les statistiques mémoire"""
|
||||
return SystemService.get_memory_usage()
|
||||
|
||||
|
||||
@router.get("/processes")
|
||||
async def get_processes(limit: int = 10, current_user: User = Depends(get_current_user)):
|
||||
"""Récupère la liste des processus actifs"""
|
||||
return SystemService.get_top_processes(limit=limit)
|
||||
|
||||
|
||||
@router.get("/disks", response_model=BlockDevicesInfo)
|
||||
async def get_block_devices(current_user: User = Depends(get_current_user)):
|
||||
"""Récupère les informations sur les disques et partitions avec lsblk"""
|
||||
return SystemService.get_block_devices()
|
||||
10
backend/app/api/routes.py
Normal file
10
backend/app/api/routes.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from fastapi import APIRouter
|
||||
from app.api.endpoints import auth, system, docker, packages, shortcuts
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["authentication"])
|
||||
api_router.include_router(system.router, prefix="/system", tags=["system"])
|
||||
api_router.include_router(docker.router, prefix="/docker", tags=["docker"])
|
||||
api_router.include_router(packages.router, prefix="/packages", tags=["packages"])
|
||||
api_router.include_router(shortcuts.router, prefix="/shortcuts", tags=["shortcuts"])
|
||||
59
backend/app/api/websocket.py
Normal file
59
backend/app/api/websocket.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import asyncio
|
||||
import json
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
from app.services.system import SystemService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.websocket("/ws/system")
|
||||
async def websocket_system_stats(websocket: WebSocket):
|
||||
"""
|
||||
WebSocket endpoint pour stream les stats système en temps réel
|
||||
"""
|
||||
await websocket.accept()
|
||||
|
||||
# Initialiser le cache psutil
|
||||
import psutil
|
||||
psutil.cpu_percent(interval=0.05, percpu=True)
|
||||
|
||||
try:
|
||||
while True:
|
||||
# Récupère les stats (très rapide avec cache)
|
||||
stats = SystemService.get_system_stats()
|
||||
|
||||
# Envoie au client
|
||||
await websocket.send_json({
|
||||
"cpu": {
|
||||
"percent": stats.cpu.percent,
|
||||
"average": stats.cpu.average,
|
||||
"cores": stats.cpu.cores,
|
||||
"per_cpu": stats.cpu.per_cpu
|
||||
},
|
||||
"memory": {
|
||||
"percent": stats.memory.percent,
|
||||
"used": stats.memory.used,
|
||||
"total": stats.memory.total,
|
||||
"available": stats.memory.available
|
||||
},
|
||||
"processes": [
|
||||
{
|
||||
"pid": p.pid,
|
||||
"name": p.name,
|
||||
"status": p.status,
|
||||
"cpu_percent": p.cpu_percent,
|
||||
"memory_percent": p.memory_percent,
|
||||
"username": p.username
|
||||
}
|
||||
for p in stats.processes
|
||||
]
|
||||
})
|
||||
|
||||
# Attendre 1 seconde avant le prochain envoi (1 fps, comme GNOME Monitor)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except WebSocketDisconnect:
|
||||
print(f"Client déconnecté")
|
||||
except Exception as e:
|
||||
print(f"Erreur WebSocket: {e}")
|
||||
await websocket.close(code=1000)
|
||||
Reference in New Issue
Block a user