cc4c3fcecb
- Backend: Customer/Satellite Models, customer_id auf Server/Job/Audit - Satellite-API: heartbeat, poll (atomares Claiming), logs, result, scan-result, health-report - Auth via X-Api-Key (SHA-256 gehasht) - Job-Queue: pending/claimed/running/success/failed + Stale-Janitor - Batch-Trigger: ein Job pro Server, Satellite arbeitet sequenziell ab - Credentials bleiben lokal: nur symbolische credential_ref zentral - Neues Paket satellite/: Pull-Loop, WinRM/SSH/CAU/Scanner, PyInstaller-tauglich - Frontend: Kunden-Switcher, Satelliten-View, Polling statt WebSocket - Entfernt: WebSocket/Socket.io, Redis, zentrale Credentials, JobRunner - Docs: README/AGENTS/PROMPT auf neue Architektur aktualisiert
121 lines
3.6 KiB
Python
121 lines
3.6 KiB
Python
"""Server inventory routes (customer-scoped).
|
|
|
|
No direct connectivity from the central server - health checks are
|
|
HEALTH_CHECK jobs executed by the customer's satellite.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.api.deps import client_ip, get_current_user
|
|
from app.core.database import get_db
|
|
from app.core.exceptions import ConflictError, NotFoundError
|
|
from app.models.customer import Customer
|
|
from app.models.server import Server
|
|
from app.models.user import User
|
|
from app.schemas.server import ServerCreate, ServerRead, ServerUpdate
|
|
from app.services.audit import AuditService
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("", response_model=list[ServerRead])
|
|
async def list_servers(
|
|
customer_id: int | None = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: User = Depends(get_current_user),
|
|
) -> list[Server]:
|
|
stmt = select(Server).order_by(Server.name)
|
|
if customer_id is not None:
|
|
stmt = stmt.where(Server.customer_id == customer_id)
|
|
result = await db.execute(stmt)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
@router.post("", response_model=ServerRead, status_code=201)
|
|
async def create_server(
|
|
payload: ServerCreate,
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> Server:
|
|
customer = await db.get(Customer, payload.customer_id)
|
|
if not customer:
|
|
raise NotFoundError("Kunde nicht gefunden")
|
|
|
|
existing = await db.execute(
|
|
select(Server).where(
|
|
Server.customer_id == payload.customer_id, Server.name == payload.name
|
|
)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
raise ConflictError("Server mit diesem Namen existiert beim Kunden bereits")
|
|
|
|
server = Server(**payload.model_dump())
|
|
db.add(server)
|
|
await db.flush()
|
|
await AuditService(db).log(
|
|
username=user.username,
|
|
action="server.create",
|
|
target=server.name,
|
|
customer_id=customer.id,
|
|
ip_address=client_ip(request),
|
|
)
|
|
return server
|
|
|
|
|
|
@router.get("/{server_id}", response_model=ServerRead)
|
|
async def get_server(
|
|
server_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: User = Depends(get_current_user),
|
|
) -> Server:
|
|
server = await db.get(Server, server_id)
|
|
if not server:
|
|
raise NotFoundError("Server nicht gefunden")
|
|
return server
|
|
|
|
|
|
@router.patch("/{server_id}", response_model=ServerRead)
|
|
async def update_server(
|
|
server_id: int,
|
|
payload: ServerUpdate,
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> Server:
|
|
server = await db.get(Server, server_id)
|
|
if not server:
|
|
raise NotFoundError("Server nicht gefunden")
|
|
for field, value in payload.model_dump(exclude_unset=True).items():
|
|
setattr(server, field, value)
|
|
await AuditService(db).log(
|
|
username=user.username,
|
|
action="server.update",
|
|
target=server.name,
|
|
customer_id=server.customer_id,
|
|
ip_address=client_ip(request),
|
|
)
|
|
return server
|
|
|
|
|
|
@router.delete("/{server_id}", status_code=204)
|
|
async def delete_server(
|
|
server_id: int,
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> None:
|
|
server = await db.get(Server, server_id)
|
|
if not server:
|
|
raise NotFoundError("Server nicht gefunden")
|
|
await AuditService(db).log(
|
|
username=user.username,
|
|
action="server.delete",
|
|
target=server.name,
|
|
customer_id=server.customer_id,
|
|
ip_address=client_ip(request),
|
|
)
|
|
await db.delete(server)
|