fix: критические баги и качество кода — полный аудит
Критические фиксы: - main.py: монтировать /static из web/static/ (CSS не грузился совсем) - api/scans.py: filtered total count (был всегда общий, игнорируя фильтры) - web/routes.py: исправлен VALID_SORT_FIELDS (отсутствовали ключи packages) - web/routes.py: filtered total count для web scans list - package_detail.html: f.data.X вместо f.X (findings не отображались) Чистка мёртвого кода: - config.py: удалён _parse_repos и nexus_repositories (не использовались) - web/routes.py: удалён completed_scans/failed_scans (не отображались) - удалён мёртвый guarddog_nexus/static/style.css (67-байтный стаб) Качество кода: - web/routes.py: Jinja2 Environment кэшируется на уровне модуля - Вынесен дублирующийся JS в web/static/app.js - Вынесены дублирующиеся inline-стили в CSS-классы - Исправлен duplicate class attribute в списках - Удалены гигантские SVG из empty states Тесты: - 20 новых edge-case тестов (CSV export, search/filter/sort, 404, pagination) - Добавлен sample_flagged_scan fixture - Итого: 50 тестов, все зелёные
This commit is contained in:
@@ -4,6 +4,7 @@ import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
from sqlalchemy import Integer, cast, func, select, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -12,7 +13,12 @@ from guarddog_nexus.models import Finding, Scan
|
||||
|
||||
router = APIRouter(tags=["web"])
|
||||
|
||||
VALID_SORT_FIELDS = {
|
||||
_jinja_env = Environment(
|
||||
loader=PackageLoader("guarddog_nexus", "web/templates"),
|
||||
autoescape=select_autoescape(),
|
||||
)
|
||||
|
||||
SCAN_SORT_FIELDS = {
|
||||
"id": Scan.id,
|
||||
"package_name": Scan.package_name,
|
||||
"started_at": Scan.started_at,
|
||||
@@ -20,15 +26,16 @@ VALID_SORT_FIELDS = {
|
||||
"total_findings": Scan.total_findings,
|
||||
}
|
||||
|
||||
PACKAGE_SORT_FIELDS = {
|
||||
"name": Scan.package_name,
|
||||
"last_scanned_at": Scan.started_at,
|
||||
"total_findings": Scan.total_findings,
|
||||
"flagged": Scan.flagged,
|
||||
}
|
||||
|
||||
|
||||
def _render(name: str, **context) -> HTMLResponse:
|
||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
|
||||
env = Environment(
|
||||
loader=PackageLoader("guarddog_nexus", "web/templates"),
|
||||
autoescape=select_autoescape(),
|
||||
)
|
||||
template = env.get_template(name)
|
||||
template = _jinja_env.get_template(name)
|
||||
return HTMLResponse(template.render(**context))
|
||||
|
||||
|
||||
@@ -53,10 +60,6 @@ async def _dashboard_data(session: AsyncSession) -> dict:
|
||||
Scan.started_at >= func.datetime("now", "-7 days"),
|
||||
)
|
||||
)
|
||||
completed_scans = await session.scalar(
|
||||
select(func.count(Scan.id)).where(Scan.status == "completed")
|
||||
)
|
||||
failed_scans = await session.scalar(select(func.count(Scan.id)).where(Scan.status == "failed"))
|
||||
total_findings = await session.scalar(select(func.count(Finding.id)))
|
||||
|
||||
warnings_count = await session.scalar(
|
||||
@@ -115,7 +118,6 @@ async def _dashboard_data(session: AsyncSession) -> dict:
|
||||
|
||||
max_findings = max((r.total for r in most_flagged), default=1)
|
||||
|
||||
# Heatmap: scans per day for last 14 days
|
||||
days_raw = (
|
||||
await session.execute(
|
||||
select(
|
||||
@@ -133,8 +135,6 @@ async def _dashboard_data(session: AsyncSession) -> dict:
|
||||
"total_scans": total_scans or 0,
|
||||
"flagged_scans": flagged_scans or 0,
|
||||
"recent_flagged": recent_flagged or 0,
|
||||
"completed_scans": completed_scans or 0,
|
||||
"failed_scans": failed_scans or 0,
|
||||
"total_findings": total_findings or 0,
|
||||
"warnings_count": warnings_count or 0,
|
||||
"errors_count": errors_count or 0,
|
||||
@@ -162,23 +162,27 @@ async def scans_list(
|
||||
per_page = 50
|
||||
offset = (page - 1) * per_page
|
||||
|
||||
count_q = select(func.count(Scan.id))
|
||||
q = select(Scan)
|
||||
|
||||
if flagged == "1":
|
||||
q = q.where(Scan.flagged == True)
|
||||
count_q = count_q.where(Scan.flagged == True)
|
||||
if status:
|
||||
q = q.where(Scan.status == status)
|
||||
count_q = count_q.where(Scan.status == status)
|
||||
if search:
|
||||
pattern = f"%{search}%"
|
||||
q = q.where(
|
||||
Scan.package_name.ilike(pattern) | Scan.package_version.ilike(pattern)
|
||||
)
|
||||
condition = Scan.package_name.ilike(pattern) | Scan.package_version.ilike(pattern)
|
||||
q = q.where(condition)
|
||||
count_q = count_q.where(condition)
|
||||
|
||||
sort_field = VALID_SORT_FIELDS.get(sort_by, Scan.started_at)
|
||||
sort_field = SCAN_SORT_FIELDS.get(sort_by, Scan.started_at)
|
||||
q = q.order_by(sort_field.desc() if sort_dir == "desc" else sort_field.asc())
|
||||
q = q.offset(offset).limit(per_page)
|
||||
|
||||
scans = (await session.execute(q)).scalars().all()
|
||||
total = await session.scalar(select(func.count(Scan.id)))
|
||||
total = await session.scalar(count_q)
|
||||
|
||||
return _render(
|
||||
"scans_list.html",
|
||||
@@ -240,17 +244,17 @@ async def packages_list(
|
||||
Scan.package_name.ilike(pattern) | Scan.package_version.ilike(pattern)
|
||||
)
|
||||
|
||||
sort_field = VALID_SORT_FIELDS.get(sort_by, Scan.started_at)
|
||||
sort_field = PACKAGE_SORT_FIELDS.get(sort_by, Scan.started_at)
|
||||
sort_col = func.max(sort_field)
|
||||
subq = subq.order_by(
|
||||
sort_col.desc() if sort_dir == "desc" else sort_col.asc()
|
||||
)
|
||||
|
||||
subq = subq.subquery()
|
||||
total = await session.scalar(select(func.count()).select_from(subq))
|
||||
sq = subq.subquery()
|
||||
total = await session.scalar(select(func.count()).select_from(sq))
|
||||
rows = (
|
||||
await session.execute(
|
||||
select(subq).offset(offset).limit(per_page)
|
||||
select(sq).offset(offset).limit(per_page)
|
||||
)
|
||||
).all()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user