Files
site_aegisone/py_service/app/main.py
T
2026-05-17 05:22:06 +03:00

188 lines
7.1 KiB
Python

import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, Depends
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from starlette.middleware.sessions import SessionMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import get_settings
from app.database import get_db, async_session
from app.auth import audit_log
from app.services.docs_permissions import discover_docs
settings = get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
async with async_session() as db:
from app.calculations.engine import reload_coefficients
await reload_coefficients(db)
discover_docs()
yield
app = FastAPI(title="AegisOne Service Portal", lifespan=lifespan)
app.add_middleware(SessionMiddleware, secret_key=settings.SECRET_KEY, max_age=settings.SESSION_TTL)
templates = Jinja2Templates(directory="app/templates")
templates.env.globals["SITE_NAME"] = "AegisOne Engineering"
class RoleGuardMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
path = request.url.path
if path.startswith("/service/") and not any(
path.startswith(p) for p in ["/service/login", "/service/static", "/service/api/"]
):
user = request.session.get("user")
if not user:
return RedirectResponse(url="/service/login", status_code=302)
response = await call_next(request)
return response
app.add_middleware(RoleGuardMiddleware)
def get_current_user(request: Request) -> dict | None:
return request.session.get("user")
def require_role(*roles: str):
def dependency(request: Request, user: dict = Depends(get_current_user)):
if not user:
return RedirectResponse(url="/service/login", status_code=302)
if roles and user["role"] not in roles:
return HTMLResponse(content="Доступ запрещён", status_code=403)
return user
return dependency
@app.get("/service/login")
async def login_page(request: Request, db: AsyncSession = Depends(get_db)):
if request.session.get("user"):
return RedirectResponse(url="/service/dashboard", status_code=302)
return templates.TemplateResponse("login.html", {"request": request, "error": ""})
@app.post("/service/login")
async def login_submit(request: Request, db: AsyncSession = Depends(get_db)):
form = await request.form()
login = form.get("login", "").strip()
password = form.get("password", "")
if not login or not password:
return templates.TemplateResponse("login.html", {"request": request, "error": "Заполните все поля."})
from app.auth import authenticate
ip = request.client.host if request.client else ""
result = await authenticate(db, login, password, ip)
if result["success"]:
request.session["user"] = result["user"]
request.session["csrf_token"] = __import__("secrets").token_hex(32)
redirect = request.query_params.get("redirect", "/service/dashboard")
return RedirectResponse(url=redirect, status_code=302)
return templates.TemplateResponse("login.html", {"request": request, "error": result["error"]})
@app.get("/service/logout")
async def logout(request: Request, db: AsyncSession = Depends(get_db)):
user = request.session.get("user")
if user:
await audit_log(db, user["id"], "logout", "Выход из системы", request.client.host if request.client else "")
request.session.clear()
return RedirectResponse(url="/service/login", status_code=302)
@app.get("/service/dashboard")
async def dashboard(request: Request, db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
from sqlalchemy import select, func
from app.models.models import Object, Task, Incident, SLAContract, SHSRecord
stats = {}
result = await db.execute(select(func.count(Object.id)).where(Object.status == "active"))
stats["objects"] = result.scalar() or 0
result = await db.execute(select(func.count(Task.id)).where(Task.status.in_(["open", "in_progress"])))
stats["tasks"] = result.scalar() or 0
result = await db.execute(select(func.count(Incident.id)).where(Incident.status.in_(["open", "in_progress"])))
stats["incidents"] = result.scalar() or 0
result = await db.execute(select(func.count(SLAContract.id)).where(SLAContract.status == "active"))
stats["sla_count"] = result.scalar() or 0
result = await db.execute(select(SHSRecord.shs_score, SHSRecord.shs_status).order_by(SHSRecord.id.desc()).limit(1))
shs_row = result.first()
stats["shs"] = {"shs_score": shs_row[0], "shs_status": shs_row[1]} if shs_row else None
if user["role"] in ("engineer", "technician"):
result = await db.execute(
select(func.count(Task.id)).where(
Task.assigned_to == user["id"],
Task.status.in_(["open", "in_progress"]),
)
)
stats["my_tasks"] = result.scalar() or 0
result = await db.execute(
select(func.count(Task.id), func.sum(Task.sla_compliant.cast(int)))
.where(Task.closed_at.isnot(None))
)
row = result.first()
total = row[0] or 0
compliant = row[1] or 0
stats["sla_compliance"] = round((compliant / total) * 100, 1) if total > 0 else 0
shs_alert = None
if stats["shs"] and stats["shs"]["shs_score"] < 70:
shs_alert = f"SHS ниже 70 ({stats['shs']['shs_score']}) — {stats['shs']['shs_status']}"
now = __import__("datetime").datetime.now().strftime("%d.%m.%Y %H:%M")
return templates.TemplateResponse("dashboard.html", {
"request": request, "stats": stats, "shs_alert": shs_alert, "now": now, "user": user,
})
@app.get("/service/api/shs")
async def api_shs(db: AsyncSession = Depends(get_db)):
from sqlalchemy import select
from app.models.models import SHSRecord
result = await db.execute(select(SHSRecord).order_by(SHSRecord.id.desc()).limit(1))
record = result.scalar_one_or_none()
if not record:
return JSONResponse({"error": "No data"})
return JSONResponse({
"shs_score": float(record.shs_score),
"shs_status": record.shs_status,
"period_start": record.period_start.isoformat(),
"period_end": record.period_end.isoformat(),
})
@app.get("/service/api/tasks")
async def api_tasks(db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
from sqlalchemy import select
from app.models.models import Task
query = select(Task).where(Task.status.in_(["open", "in_progress"]))
if user["role"] == "technician":
query = query.where(Task.assigned_to == user["id"])
result = await db.execute(query.order_by(Task.created_at.desc()))
tasks = result.scalars().all()
return JSONResponse([
{
"id": t.id, "title": t.title, "priority": t.priority,
"status": t.status, "object_id": t.object_id,
}
for t in tasks
])
@app.get("/health")
async def health():
return {"status": "ok"}