feat: v1.1.3 — dashboard alerts panel, sparkline charts, log sanitisation, CSS vars
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, distinct
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ..db import get_db
|
||||
from ..models import ProxmoxServer, AuditLog
|
||||
from ..models import ProxmoxServer, AuditLog, User
|
||||
from ..template_helpers import add_i18n_context
|
||||
from ..rbac.decorators import PermissionChecker
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
@@ -72,6 +73,37 @@ def dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
return templates.TemplateResponse("dashboard.html", context)
|
||||
|
||||
|
||||
@router.get("/api/dashboard/alerts")
|
||||
def get_dashboard_alerts(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(PermissionChecker("logs.view"))
|
||||
):
|
||||
"""Вернуть события error/critical за последние 24 ч для дашборда"""
|
||||
since_24h = datetime.utcnow() - timedelta(hours=24)
|
||||
alerts = db.query(AuditLog).filter(
|
||||
AuditLog.level.in_(['error', 'critical']),
|
||||
AuditLog.created_at >= since_24h
|
||||
).order_by(AuditLog.created_at.desc()).limit(50).all()
|
||||
|
||||
return JSONResponse(content={
|
||||
"alerts": [
|
||||
{
|
||||
"id": a.id,
|
||||
"level": a.level,
|
||||
"category": a.category,
|
||||
"action": a.action,
|
||||
"message": a.message,
|
||||
"username": a.username,
|
||||
"resource_name": a.resource_name,
|
||||
"server_name": a.server_name,
|
||||
"created_at": a.created_at.isoformat() if a.created_at else None,
|
||||
}
|
||||
for a in alerts
|
||||
],
|
||||
"total": len(alerts)
|
||||
})
|
||||
|
||||
|
||||
@router.get("/containers", response_class=HTMLResponse, include_in_schema=False)
|
||||
def containers(request: Request):
|
||||
lang = request.cookies.get("language", "en")
|
||||
|
||||
@@ -113,7 +113,7 @@ def create_proxmox_server(
|
||||
db.commit()
|
||||
db.refresh(server)
|
||||
|
||||
logger.info(f"User {current_user.username} added Proxmox server: {server.name} ({server.ip_address})")
|
||||
logger.info(f"User {current_user.username} added Proxmox server: {server.name}")
|
||||
return server
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ async def auto_setup_proxmox_server(
|
||||
detail="Не удалось получить auth ticket от Proxmox"
|
||||
)
|
||||
|
||||
logger.info(f"Successfully authenticated to Proxmox {ip_address}")
|
||||
logger.info("Successfully authenticated to Proxmox")
|
||||
|
||||
except http_requests.exceptions.RequestException as e:
|
||||
logger.error(f"Connection error to Proxmox: {e}")
|
||||
@@ -227,7 +227,7 @@ async def auto_setup_proxmox_server(
|
||||
else:
|
||||
token_data = token_response.json().get("data", {})
|
||||
token_value = token_data.get("value")
|
||||
logger.info(f"Created API token: {token_name}")
|
||||
logger.info("Created API token successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error creating token: {e}, will use password auth")
|
||||
@@ -346,7 +346,7 @@ async def auto_setup_proxmox_server(
|
||||
db.commit()
|
||||
db.refresh(server)
|
||||
|
||||
logger.info(f"User {current_user.username} auto-setup Proxmox server: {server.name} ({server.ip_address})")
|
||||
logger.info(f"User {current_user.username} auto-setup Proxmox server: {server.name}")
|
||||
|
||||
return {
|
||||
"cluster": False,
|
||||
@@ -624,7 +624,7 @@ def get_all_resources(
|
||||
|
||||
for server in proxmox_servers:
|
||||
try:
|
||||
logger.info(f"Getting resources from server {server.name} ({server.ip_address}), use_password={server.use_password}")
|
||||
logger.info(f"Getting resources from server {server.name}, use_password={server.use_password}")
|
||||
# Создаём клиента для каждого сервера независимо
|
||||
if server.use_password:
|
||||
resources = get_proxmox_resources(
|
||||
@@ -634,7 +634,7 @@ def get_all_resources(
|
||||
verify_ssl=server.verify_ssl
|
||||
)
|
||||
else:
|
||||
logger.info(f"Using API token for {server.name}: user={server.api_user}, token_name={server.api_token_name}")
|
||||
logger.info(f"Using API token for {server.name}: user={server.api_user}")
|
||||
resources = get_proxmox_resources(
|
||||
host=server.ip_address,
|
||||
user=server.api_user,
|
||||
|
||||
@@ -1206,7 +1206,7 @@ def get_server_nodes(
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info(f"Nodes for server {server_id} ({server.name}), cluster_id={cluster_id}: {nodes}")
|
||||
logger.info(f"Nodes for server {server_id} ({server.name}), cluster_id={cluster_id}: {len(nodes)} node(s)")
|
||||
return JSONResponse(content={"nodes": nodes, "cluster_id": cluster_id})
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting nodes from {server.name}: {e}")
|
||||
@@ -1611,7 +1611,7 @@ async def vnc_websocket_proxy(
|
||||
encoded_ticket = urllib.parse.quote(vncticket, safe='')
|
||||
proxmox_ws_url = f"wss://{server.ip_address}:8006/api2/json/nodes/{node}/{proxmox_vmtype}/{vmid}/vncwebsocket?port={port}&vncticket={encoded_ticket}"
|
||||
|
||||
logger.info(f"Connecting to Proxmox VNC: {server.ip_address}:8006 for {proxmox_vmtype}/{vmid}")
|
||||
logger.info(f"Connecting to Proxmox VNC: {server.name} for {proxmox_vmtype}/{vmid}")
|
||||
|
||||
# SSL контекст для самоподписанных сертификатов
|
||||
ssl_context = ssl.create_default_context()
|
||||
|
||||
Reference in New Issue
Block a user