3
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import PlainTextResponse
|
||||
|
||||
from app.core.config import get_parsed_servers
|
||||
from app.models.schemas import (
|
||||
AiContextResponse,
|
||||
AiDuplicateSummary,
|
||||
AiInventoryStats,
|
||||
AiVmSummary,
|
||||
ScanResponse,
|
||||
)
|
||||
from app.services.scanner import scan_servers
|
||||
|
||||
router = APIRouter(prefix="/ai", tags=["ai"])
|
||||
|
||||
|
||||
@router.get("/context", response_model=AiContextResponse)
|
||||
async def get_ai_context() -> AiContextResponse:
|
||||
scan = await scan_servers(get_parsed_servers())
|
||||
return build_ai_context(scan)
|
||||
|
||||
|
||||
@router.get("/context.md", response_class=PlainTextResponse)
|
||||
async def get_ai_context_markdown() -> str:
|
||||
scan = await scan_servers(get_parsed_servers())
|
||||
context = build_ai_context(scan)
|
||||
return render_markdown(context)
|
||||
|
||||
|
||||
def build_ai_context(scan: ScanResponse) -> AiContextResponse:
|
||||
running = sum(1 for item in scan.items if item.status == "running")
|
||||
stopped = sum(1 for item in scan.items if item.status == "stopped")
|
||||
qemu = sum(1 for item in scan.items if item.type == "qemu")
|
||||
lxc = sum(1 for item in scan.items if item.type == "lxc")
|
||||
|
||||
return AiContextResponse(
|
||||
generated_at=datetime.now(timezone.utc).isoformat(),
|
||||
purpose=(
|
||||
"Contexte compact pour analyser l'inventaire Proxmox, les erreurs de scan "
|
||||
"et les doublons de VM/LXC."
|
||||
),
|
||||
stats=AiInventoryStats(
|
||||
total=len(scan.items),
|
||||
running=running,
|
||||
stopped=stopped,
|
||||
qemu=qemu,
|
||||
lxc=lxc,
|
||||
duplicate_groups=len(scan.duplicates),
|
||||
),
|
||||
servers=scan.servers,
|
||||
duplicates=[
|
||||
AiDuplicateSummary(
|
||||
label=group.label,
|
||||
reason=group.reason,
|
||||
count=group.count,
|
||||
vmids=[
|
||||
f"{item.server}/{item.node}/{item.type}/{item.vmid}"
|
||||
for item in group.items
|
||||
],
|
||||
)
|
||||
for group in scan.duplicates
|
||||
],
|
||||
inventory=[
|
||||
AiVmSummary(
|
||||
server=item.server,
|
||||
node=item.node,
|
||||
vmid=item.vmid,
|
||||
type=item.type,
|
||||
name=item.name,
|
||||
status=item.status,
|
||||
tags=item.tags,
|
||||
duplicate_id=item.duplicate_id,
|
||||
)
|
||||
for item in scan.items
|
||||
],
|
||||
notes=[
|
||||
"Les secrets API Proxmox ne sont jamais exposes par cette route.",
|
||||
"Les champs duplicate_id dependent de la lecture des descriptions Proxmox.",
|
||||
"Une erreur de serveur indique souvent un probleme reseau, TLS, token ou ACL.",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def render_markdown(context: AiContextResponse) -> str:
|
||||
lines = [
|
||||
"# Prox Visualizer AI Context",
|
||||
"",
|
||||
f"Generated at: `{context.generated_at}`",
|
||||
"",
|
||||
"## Stats",
|
||||
"",
|
||||
f"- Total VM/LXC: {context.stats.total}",
|
||||
f"- Running: {context.stats.running}",
|
||||
f"- Stopped: {context.stats.stopped}",
|
||||
f"- QEMU VM: {context.stats.qemu}",
|
||||
f"- LXC: {context.stats.lxc}",
|
||||
f"- Duplicate groups: {context.stats.duplicate_groups}",
|
||||
"",
|
||||
"## Servers",
|
||||
"",
|
||||
]
|
||||
|
||||
for server in context.servers:
|
||||
state = "OK" if server.ok else "ERROR"
|
||||
lines.append(f"- {server.name}: {state}, {server.vm_count} VM/LXC, `{server.url}`")
|
||||
for error in server.errors:
|
||||
lines.append(f" - Error: {error}")
|
||||
|
||||
lines.extend(["", "## Duplicates", ""])
|
||||
if context.duplicates:
|
||||
for duplicate in context.duplicates:
|
||||
vmids = ", ".join(duplicate.vmids)
|
||||
lines.append(
|
||||
f"- {duplicate.label} ({duplicate.reason}, x{duplicate.count}): {vmids}"
|
||||
)
|
||||
else:
|
||||
lines.append("- None")
|
||||
|
||||
lines.extend(["", "## Inventory", ""])
|
||||
for item in context.inventory:
|
||||
tags = f", tags={item.tags}" if item.tags else ""
|
||||
duplicate_id = (
|
||||
f", duplicate_id={item.duplicate_id}"
|
||||
if item.duplicate_id is not None
|
||||
else ""
|
||||
)
|
||||
lines.append(
|
||||
f"- {item.server}/{item.node}/{item.type}/{item.vmid}: "
|
||||
f"{item.name}, status={item.status or 'unknown'}{tags}{duplicate_id}"
|
||||
)
|
||||
|
||||
lines.extend(["", "## Notes", ""])
|
||||
for note in context.notes:
|
||||
lines.append(f"- {note}")
|
||||
|
||||
return "\n".join(lines) + "\n"
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.config import get_parsed_servers
|
||||
from app.models.schemas import ServerInfo
|
||||
from app.services.scanner import parse_server_configs
|
||||
|
||||
@@ -9,7 +9,7 @@ router = APIRouter(prefix="/proxmox", tags=["proxmox"])
|
||||
|
||||
@router.get("/servers", response_model=list[ServerInfo])
|
||||
async def list_servers() -> list[ServerInfo]:
|
||||
servers, _ = parse_server_configs(settings.parsed_servers)
|
||||
servers, _ = parse_server_configs(get_parsed_servers())
|
||||
return [
|
||||
ServerInfo(
|
||||
name=server.name,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.config import get_parsed_servers
|
||||
from app.models.schemas import ScanResponse
|
||||
from app.services.scanner import scan_servers
|
||||
|
||||
@@ -9,4 +9,4 @@ router = APIRouter(prefix="/vms", tags=["vms"])
|
||||
|
||||
@router.get("/scan", response_model=ScanResponse)
|
||||
async def scan_vms() -> ScanResponse:
|
||||
return await scan_servers(settings.parsed_servers)
|
||||
return await scan_servers(get_parsed_servers())
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.endpoints import proxmox, vms
|
||||
from app.api.endpoints import ai, proxmox, vms
|
||||
|
||||
api_router = APIRouter(prefix="/api")
|
||||
api_router.include_router(ai.router)
|
||||
api_router.include_router(proxmox.router)
|
||||
api_router.include_router(vms.router)
|
||||
|
||||
Reference in New Issue
Block a user