new changes
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,6 @@
|
||||
"""YakPanel - Dashboard API"""
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
@@ -10,6 +12,24 @@ from app.models.user import User
|
||||
router = APIRouter(prefix="/dashboard", tags=["dashboard"])
|
||||
|
||||
|
||||
def _root_inode_usage() -> dict:
|
||||
"""Inode use on filesystem root (Linux). Returns percent or null if unavailable."""
|
||||
try:
|
||||
sv = os.statvfs("/")
|
||||
except (AttributeError, OSError):
|
||||
return {"percent": None, "used": None, "total": None}
|
||||
total = int(sv.f_files)
|
||||
free = int(sv.f_ffree)
|
||||
if total <= 0:
|
||||
return {"percent": None, "used": None, "total": None}
|
||||
used = max(0, total - free)
|
||||
return {
|
||||
"percent": round(100.0 * used / total, 1),
|
||||
"used": used,
|
||||
"total": total,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/stats")
|
||||
async def get_stats(
|
||||
current_user: User = Depends(get_current_user),
|
||||
@@ -18,10 +38,10 @@ async def get_stats(
|
||||
"""Get dashboard statistics"""
|
||||
import psutil
|
||||
|
||||
from app.services.site_service import get_site_count
|
||||
from app.services.site_service import get_site_count, ssl_alert_summary
|
||||
from app.models.ftp import Ftp
|
||||
from app.models.database import Database
|
||||
from sqlalchemy import select, func
|
||||
|
||||
site_count = await get_site_count(db)
|
||||
ftp_result = await db.execute(select(func.count()).select_from(Ftp))
|
||||
ftp_count = ftp_result.scalar() or 0
|
||||
@@ -32,11 +52,14 @@ async def get_stats(
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
memory = psutil.virtual_memory()
|
||||
disk = psutil.disk_usage("/")
|
||||
inodes = _root_inode_usage()
|
||||
ssl_alerts = await ssl_alert_summary(db)
|
||||
|
||||
return {
|
||||
"site_count": site_count,
|
||||
"ftp_count": ftp_count,
|
||||
"database_count": database_count,
|
||||
"ssl_alerts": ssl_alerts,
|
||||
"system": {
|
||||
"cpu_percent": cpu_percent,
|
||||
"memory_percent": memory.percent,
|
||||
@@ -45,5 +68,8 @@ async def get_stats(
|
||||
"disk_percent": disk.percent,
|
||||
"disk_used_gb": round(disk.used / 1024 / 1024 / 1024, 2),
|
||||
"disk_total_gb": round(disk.total / 1024 / 1024 / 1024, 2),
|
||||
"inode_percent": inodes["percent"],
|
||||
"inode_used": inodes["used"],
|
||||
"inode_total": inodes["total"],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,6 +20,38 @@ class CreateFirewallRuleRequest(BaseModel):
|
||||
ps: str = ""
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
async def firewall_backend_status(current_user: User = Depends(get_current_user)):
|
||||
"""UFW and firewalld presence/state for the Security UI (read-only)."""
|
||||
ufw_out, _ = exec_shell_sync("ufw status 2>/dev/null", timeout=5)
|
||||
ufw_text = (ufw_out or "").strip()
|
||||
ufw_detected = bool(ufw_text) and "Status:" in ufw_text
|
||||
ufw_active: bool | None = None
|
||||
if ufw_detected:
|
||||
if "Status: active" in ufw_text:
|
||||
ufw_active = True
|
||||
elif "Status: inactive" in ufw_text:
|
||||
ufw_active = False
|
||||
|
||||
fw_state_out, _ = exec_shell_sync("firewall-cmd --state 2>/dev/null", timeout=5)
|
||||
fw_line = (fw_state_out or "").strip().lower()
|
||||
firewalld_running = fw_line == "running"
|
||||
firewalld_detected = fw_line in ("running", "not running")
|
||||
|
||||
return {
|
||||
"ufw": {
|
||||
"detected": ufw_detected,
|
||||
"active": ufw_active,
|
||||
"summary_line": ufw_text.split("\n")[0] if ufw_text else "",
|
||||
},
|
||||
"firewalld": {
|
||||
"detected": firewalld_detected,
|
||||
"running": firewalld_running,
|
||||
"state": fw_line or None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/list")
|
||||
async def firewall_list(
|
||||
current_user: User = Depends(get_current_user),
|
||||
|
||||
Binary file not shown.
@@ -626,6 +626,34 @@ async def get_site_count(db: AsyncSession) -> int:
|
||||
return result.scalar() or 0
|
||||
|
||||
|
||||
async def ssl_alert_summary(db: AsyncSession) -> dict:
|
||||
"""Sites with LE certs expiring soon or expired (for dashboard banners)."""
|
||||
result = await db.execute(select(Site).order_by(Site.id))
|
||||
sites = result.scalars().all()
|
||||
expired: list[dict] = []
|
||||
expiring: list[dict] = []
|
||||
for s in sites:
|
||||
domain_result = await db.execute(select(Domain).where(Domain.pid == s.id).order_by(Domain.id))
|
||||
domain_rows = domain_result.scalars().all()
|
||||
hostnames = [d.name for d in domain_rows]
|
||||
if not hostnames:
|
||||
continue
|
||||
ssl = _best_ssl_for_hostnames(hostnames)
|
||||
if ssl["status"] == "expired":
|
||||
expired.append({
|
||||
"site": s.name,
|
||||
"primary": hostnames[0],
|
||||
"days_left": ssl.get("days_left"),
|
||||
})
|
||||
elif ssl["status"] == "expiring":
|
||||
expiring.append({
|
||||
"site": s.name,
|
||||
"primary": hostnames[0],
|
||||
"days_left": ssl.get("days_left"),
|
||||
})
|
||||
return {"expired": expired, "expiring": expiring}
|
||||
|
||||
|
||||
async def get_site_with_domains(db: AsyncSession, site_id: int) -> dict | None:
|
||||
"""Get site with domain list for editing."""
|
||||
result = await db.execute(select(Site).where(Site.id == site_id))
|
||||
|
||||
Reference in New Issue
Block a user