fix: CPU calc, disk card, lifespan, remove Google Fonts CDN, fix doc link

- Fix Docker container CPU % (multiply by num_cpus)
- Add missing Disk usage card to system status grid
- Replace deprecated @app.on_event with lifespan context manager
- Move `import requests` to module level
- Remove Google Fonts CDN dependency from CSS
- Update naming doc URL to real raw Gitea link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
unit0 2026-03-17 10:34:22 -04:00
parent 411038582a
commit 5b1eb9eec4
5 changed files with 19 additions and 8 deletions

View File

@ -42,9 +42,10 @@ class DockerMonitor:
stats["cpu_stats"]["system_cpu_usage"] stats["cpu_stats"]["system_cpu_usage"]
- stats["precpu_stats"]["system_cpu_usage"] - stats["precpu_stats"]["system_cpu_usage"]
) )
num_cpus = len(stats["cpu_stats"]["cpu_usage"].get("percpu_usage") or []) or 1
cpu_percent = 0.0 cpu_percent = 0.0
if system_delta > 0: if system_delta > 0:
cpu_percent = (cpu_delta / system_delta) * 100.0 cpu_percent = (cpu_delta / system_delta) * num_cpus * 100.0
# Calculate memory usage # Calculate memory usage
memory_usage = stats["memory_stats"].get("usage", 0) memory_usage = stats["memory_stats"].get("usage", 0)

View File

@ -2,10 +2,12 @@ import asyncio
import json import json
import logging import logging
import time import time
from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from typing import List from typing import List
import psutil import psutil
import requests
import yaml import yaml
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect, HTTPException from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect, HTTPException
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
@ -16,7 +18,7 @@ from .docker_monitor import DockerMonitor
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
app = FastAPI(title="RedUnits Control Panel") app = FastAPI(title="RedUnits Control Panel", lifespan=lifespan)
# Setup paths # Setup paths
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
@ -156,10 +158,11 @@ async def metrics_loop():
# App lifecycle # App lifecycle
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@app.on_event("startup") @asynccontextmanager
async def startup_event(): async def lifespan(app: FastAPI):
asyncio.create_task(metrics_loop()) asyncio.create_task(metrics_loop())
logger.info("Metrics background task started") logger.info("Metrics background task started")
yield
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@ -265,7 +268,6 @@ async def get_document_content(doc_id: str):
if not doc: if not doc:
raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found") raise HTTPException(status_code=404, detail=f"Document '{doc_id}' not found")
import requests
try: try:
resp = await asyncio.to_thread(requests.get, doc["url"], timeout=10) resp = await asyncio.to_thread(requests.get, doc["url"], timeout=10)
resp.raise_for_status() resp.raise_for_status()

View File

@ -27,4 +27,4 @@ documents:
- id: naming-rules - id: naming-rules
title: "Правила именования устройств" title: "Правила именования устройств"
description: "Стандарт формирования имён для хостов и оборудования" description: "Стандарт формирования имён для хостов и оборудования"
url: "https://vault.redunits.net/user/repo/raw/branch/main/naming.md" # Замени на реальную ссылку (Raw format) url: "https://vault.redunits.net/SARMATA5/codex/raw/branch/main/sarmata5/naming.md"

View File

@ -1,5 +1,3 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
:root { :root {
/* Colors inspired by the mock-up */ /* Colors inspired by the mock-up */
--bg-main: #fbfbf9; --bg-main: #fbfbf9;

View File

@ -63,6 +63,16 @@
</div> </div>
</div> </div>
<div class="stat-card">
<div class="stat-label">Disk usage</div>
<div class="stat-value" x-text="system.disk.percent + '%'"></div>
<div class="progress-bar">
<div class="progress-fill" :style="`width: ${system.disk.percent}%`"
:class="getProgressColor(system.disk.percent)"></div>
</div>
<div class="stat-detail" x-text="`${system.disk.used_gb} GB / ${system.disk.total_gb} GB`"></div>
</div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-label">Docker containers</div> <div class="stat-label">Docker containers</div>
<div class="stat-value" x-text="`${system.containers.running} / ${system.containers.total}`"></div> <div class="stat-value" x-text="`${system.containers.running} / ${system.containers.total}`"></div>