95 lines
7.5 KiB
Python
95 lines
7.5 KiB
Python
"""Bounded control-panel queries backed by installation snapshots."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import func, or_, select
|
|
|
|
from .config import settings
|
|
from .installation_identity import compute_agent_health
|
|
from .models import Agent, AgentEvent, AgentSnapshot, BlockedProduct, Installation, Job, Tenant
|
|
|
|
|
|
def _page(page: int, page_size: int):
|
|
return max(1, page), min(100, max(1, page_size))
|
|
|
|
|
|
def _iso(value):
|
|
return value.isoformat() if value else None
|
|
|
|
|
|
def installation_view(installation, agent, tenant, snapshot=None):
|
|
snapshot = snapshot or AgentSnapshot(installation_id=installation.id)
|
|
health, reason = compute_agent_health(installation, supplemental=snapshot)
|
|
return {
|
|
"agent_id": agent.id, "installation_id": installation.installation_id, "installation_db_id": installation.id,
|
|
"client": tenant.name if tenant else None, "tenant_id": installation.tenant_id, "display_name": installation.display_name,
|
|
"hostname": installation.hostname, "company_name": installation.company_name, "company_code": installation.company_code,
|
|
"company_cnpj": installation.company_cnpj_masked, "company_uf": installation.company_uf, "company_crt": installation.company_crt,
|
|
"agent_version": agent.agent_version, "version_drift": snapshot.version_drift, "health": health, "health_reason": reason,
|
|
"last_seen_at": _iso(installation.last_seen_at), "started_at": _iso(installation.started_at), "last_status": installation.last_status,
|
|
"total_products": snapshot.total_products, "fiscally_complete": snapshot.fiscally_complete, "safe_blocked": snapshot.safe_blocked,
|
|
"failed_products": snapshot.failed_products, "coverage_percent": snapshot.coverage_percent, "blockers": snapshot.blockers or {},
|
|
"last_job_status": snapshot.last_job_status, "last_job_at": _iso(snapshot.last_job_at), "last_successful_job_at": _iso(snapshot.last_successful_job_at),
|
|
"firebird_version": installation.firebird_version, "firebird_ods": installation.firebird_ods, "database_identifier": installation.database_identifier,
|
|
"capabilities": installation.capabilities or {}, "fiscal_engine_version": installation.fiscal_engine_version, "dataset_versions": installation.dataset_versions or {},
|
|
}
|
|
|
|
|
|
def list_installations(session, *, page=1, page_size=50, client=None, health=None, version=None, search=None, last_seen_before=None):
|
|
page, page_size = _page(page, page_size)
|
|
query = select(Installation, Agent, Tenant, AgentSnapshot).join(Agent, Agent.id == Installation.agent_id).join(Tenant, Tenant.id == Installation.tenant_id).outerjoin(AgentSnapshot, AgentSnapshot.installation_id == Installation.id)
|
|
if client: query = query.where(or_(Tenant.name.ilike(f"%{client}%"), Installation.customer_code.ilike(f"%{client}%")))
|
|
if version: query = query.where(Agent.agent_version == version)
|
|
if search: query = query.where(or_(Installation.display_name.ilike(f"%{search}%"), Installation.hostname.ilike(f"%{search}%"), Tenant.name.ilike(f"%{search}%")))
|
|
if last_seen_before: query = query.where(or_(Installation.last_seen_at.is_(None), Installation.last_seen_at < last_seen_before))
|
|
ordered = query.order_by(Installation.display_name.asc(), Installation.id.asc())
|
|
rows = session.execute(ordered).all() if health else session.execute(ordered.offset((page - 1) * page_size).limit(page_size)).all()
|
|
items = [installation_view(i, a, t, s) for i, a, t, s in rows]
|
|
total = None
|
|
if health:
|
|
items = [item for item in items if item["health"] == health]
|
|
total = len(items)
|
|
items = items[(page - 1) * page_size:page * page_size]
|
|
return {"page": page, "page_size": page_size, "items": items, "total": total if total is not None else len(items), "health_filter_applied": health}
|
|
|
|
|
|
def dashboard_summary(session):
|
|
query = select(Installation, Agent, Tenant, AgentSnapshot).join(Agent, Agent.id == Installation.agent_id).join(Tenant, Tenant.id == Installation.tenant_id).outerjoin(AgentSnapshot, AgentSnapshot.installation_id == Installation.id).order_by(Installation.id.asc())
|
|
items = [installation_view(i, a, t, s) for i, a, t, s in session.execute(query).all()]
|
|
counts = {state: sum(item["health"] == state for item in items) for state in ("ONLINE", "DEGRADED", "OFFLINE", "FAILED", "UNKNOWN")}
|
|
totals = {key: sum(int(item[key] or 0) for item in items) for key in ("total_products", "fiscally_complete", "safe_blocked", "failed_products")}
|
|
blocker_counts = {}
|
|
for item in items:
|
|
for key, value in (item["blockers"] or {}).items(): blocker_counts[key] = blocker_counts.get(key, 0) + int(value or 0)
|
|
return {"agents_total": len(items), **{key.lower(): value for key, value in counts.items()}, **totals, "blockers": blocker_counts, "stale_agents": [item for item in items if item["health"] == "OFFLINE"], "outdated_agents": [item for item in items if item["version_drift"] == "OUTDATED"]}
|
|
|
|
|
|
def list_jobs(session, *, agent_id=None, page=1, page_size=50, status=None, kind=None, since=None, until=None):
|
|
page, page_size = _page(page, page_size)
|
|
query = select(Job).order_by(Job.created_at.desc(), Job.id.desc())
|
|
if agent_id: query = query.where(Job.agent_id == agent_id)
|
|
if status: query = query.where(Job.status == status)
|
|
if kind: query = query.where(Job.kind == kind)
|
|
if since: query = query.where(Job.created_at >= since)
|
|
if until: query = query.where(Job.created_at <= until)
|
|
rows = session.scalars(query.offset((page - 1) * page_size).limit(page_size)).all()
|
|
return {"page": page, "page_size": page_size, "items": [{"job_id": row.id, "agent_id": row.agent_id, "kind": row.kind, "status": row.status, "created_at": _iso(row.created_at), "started_at": _iso(row.started_at), "finished_at": _iso(row.finished_at), "attempts": row.attempts, "result_summary": row.result_summary, "error": row.error} for row in rows], "total": len(rows)}
|
|
|
|
|
|
def list_blockers(session, *, tenant_id=None, agent_id=None, page=1, page_size=50, blocker_type=None, field=None, resolved=False):
|
|
page, page_size = _page(page, page_size)
|
|
query = select(BlockedProduct, Tenant).join(Tenant, Tenant.id == BlockedProduct.tenant_id).where(BlockedProduct.resolved_at.is_not(None) if resolved else BlockedProduct.resolved_at.is_(None)).order_by(BlockedProduct.last_seen_at.desc(), BlockedProduct.id.desc())
|
|
if tenant_id: query = query.where(BlockedProduct.tenant_id == tenant_id)
|
|
if blocker_type: query = query.where(BlockedProduct.blocker_type == blocker_type)
|
|
if field: query = query.where(BlockedProduct.field == field)
|
|
rows = session.execute(query.offset((page - 1) * page_size).limit(page_size)).all()
|
|
return {"page": page, "page_size": page_size, "items": [{"id": row.id, "tenant_id": row.tenant_id, "client": tenant.name, "product_code": row.product_code, "description": row.description, "field": row.field, "blocker_type": row.blocker_type, "reason": row.reason_code, "evidence_summary": row.evidence_summary, "first_seen_at": _iso(row.first_seen_at), "last_seen_at": _iso(row.last_seen_at), "retry_count": row.retry_count, "resolved_at": _iso(row.resolved_at)} for row, tenant in rows], "total": len(rows)}
|
|
|
|
|
|
def list_panel_events(session, installation_db_id, **filters):
|
|
from .events import list_events
|
|
rows = list_events(session, installation_db_id, **filters)
|
|
return [{"id": row.id, "severity": row.severity, "event_type": row.event_type, "message": row.message, "context": row.structured_context, "job_id": row.job_id, "product_code": row.product_code, "occurred_at": _iso(row.occurred_at)} for row in rows]
|