110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
"""
|
|
control/panel/health.py
|
|
-----------------------
|
|
Superadmin health / monitoring dashboard (MT-8+).
|
|
|
|
GET /health — reads control plane data only (no per-tenant DB queries)
|
|
so it stays fast regardless of tenant count.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
from flask import Blueprint, render_template, session
|
|
|
|
from control.base import control_session
|
|
from control.models import Plan, Tenant
|
|
from control.tenant_migrate import chain_head
|
|
from control.time_utils import now_eastern
|
|
from .decorators import superadmin_required
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
bp = Blueprint('health', __name__, url_prefix='/health')
|
|
|
|
_BASE_DOMAIN = lambda: os.environ.get('TENANT_BASE_DOMAIN', 'jqc.app')
|
|
|
|
|
|
@bp.route('/')
|
|
@superadmin_required
|
|
def dashboard():
|
|
try:
|
|
head = chain_head()
|
|
except Exception:
|
|
head = None
|
|
|
|
with control_session() as s:
|
|
plans = {p.id: p.name for p in s.query(Plan).all()}
|
|
tenants = s.query(Tenant).order_by(Tenant.id).all()
|
|
|
|
now = now_eastern()
|
|
rows = []
|
|
for t in tenants:
|
|
trial_days_left = None
|
|
trial_expired = False
|
|
if t.trial_ends_at:
|
|
delta = (t.trial_ends_at - now).days
|
|
trial_days_left = max(0, delta)
|
|
trial_expired = now >= t.trial_ends_at
|
|
|
|
schema_ok = (t.alembic_head == head) if head else None
|
|
|
|
rows.append({
|
|
'id': t.id,
|
|
'slug': t.slug,
|
|
'name': t.name,
|
|
'status': t.status,
|
|
'plan_name': plans.get(t.plan_id, '?'),
|
|
'subscription_status': t.subscription_status,
|
|
'trial_ends_at': t.trial_ends_at,
|
|
'trial_days_left': trial_days_left,
|
|
'trial_expired': trial_expired,
|
|
'current_period_end': t.current_period_end,
|
|
'has_stripe': bool(t.stripe_customer_id),
|
|
'alembic_head': t.alembic_head,
|
|
'schema_ok': schema_ok,
|
|
'created_at': t.created_at,
|
|
'suspended_at': t.suspended_at,
|
|
})
|
|
|
|
# ── Aggregate stats ──────────────────────────────────────────────────────
|
|
total = len(rows)
|
|
active = sum(1 for r in rows if r['status'] == 'active')
|
|
suspended = sum(1 for r in rows if r['status'] == 'suspended')
|
|
schema_behind = sum(1 for r in rows if r['schema_ok'] is False)
|
|
|
|
sub_counts = {}
|
|
for r in rows:
|
|
key = r['subscription_status'] or 'none'
|
|
sub_counts[key] = sub_counts.get(key, 0) + 1
|
|
|
|
trial_expiring_soon = sum(
|
|
1 for r in rows
|
|
if r['subscription_status'] == 'trial'
|
|
and r['trial_days_left'] is not None
|
|
and r['trial_days_left'] <= 3
|
|
and not r['trial_expired']
|
|
)
|
|
trial_expired_count = sum(
|
|
1 for r in rows
|
|
if r['subscription_status'] == 'trial' and r['trial_expired']
|
|
)
|
|
|
|
stats = {
|
|
'total': total,
|
|
'active': active,
|
|
'suspended': suspended,
|
|
'schema_behind': schema_behind,
|
|
'sub_counts': sub_counts,
|
|
'trial_expiring_soon': trial_expiring_soon,
|
|
'trial_expired': trial_expired_count,
|
|
}
|
|
|
|
return render_template(
|
|
'panel/health.html',
|
|
rows=rows,
|
|
stats=stats,
|
|
chain_head=head,
|
|
sa_username=session.get('sa_username'),
|
|
base_domain=_BASE_DOMAIN(),
|
|
)
|