Files
vpn-control-panel/app/admin/router.py
smolkik-code 7c7c88621d Initial commit: VPN Control Panel
- FastAPI REST API with JWT auth
- aiogram 3 Telegram bot with admin middleware
- APScheduler daily tasks (expiry, reminders, revoke, sync)
- SQLAlchemy 2 async ORM with Alembic migrations
- Jinja2 admin panel (Dashboard, Users, Payments, Servers, Tariffs)
- VPN provider abstraction with MockProvider
- Stats service with revenue/subscription analytics
- Docker Compose (PostgreSQL + Redis + app)
- Healthcheck endpoint
2026-07-05 17:50:11 +07:00

119 lines
4.0 KiB
Python

from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy import func, select
from app.database import async_session_factory
from app.models.payment import Payment, PaymentStatus
from app.models.server import Server
from app.models.tariff import Tariff
from app.models.user import User
from app.repositories.payment import PaymentRepository
from app.repositories.server import ServerRepository
from app.repositories.tariff import TariffRepository
from app.repositories.user import UserRepository
router = APIRouter(prefix="/admin", tags=["admin"])
templates = Jinja2Templates(directory="app/templates")
router = APIRouter(prefix="/admin", tags=["admin"])
templates = Jinja2Templates(directory="app/templates")
@router.get("/", response_class=HTMLResponse)
async def dashboard(request: Request):
async with async_session_factory() as session:
async def _count(model, *filters):
stmt = select(func.count(model.id))
for f in filters:
stmt = stmt.where(f)
return (await session.execute(stmt)).scalar_one()
total_users = await _count(User)
active_users = await _count(User, User.is_active.is_(True))
total_tariffs = await _count(Tariff)
total_servers = await _count(Server)
active_servers = await _count(Server, Server.is_active.is_(True))
total_payments = await _count(Payment)
confirmed_payments = await _count(
Payment, Payment.status == PaymentStatus.CONFIRMED
)
rev_stmt = select(func.coalesce(func.sum(Payment.amount), 0)).where(
Payment.status == PaymentStatus.CONFIRMED
)
total_revenue = float((await session.execute(rev_stmt)).scalar_one())
return templates.TemplateResponse(
"dashboard.html",
{
"request": request,
"total_users": total_users,
"active_users": active_users,
"total_tariffs": total_tariffs,
"total_servers": total_servers,
"active_servers": active_servers,
"total_payments": total_payments,
"confirmed_payments": confirmed_payments,
"total_revenue": total_revenue,
},
)
@router.get("/users", response_class=HTMLResponse)
async def users_page(request: Request):
async with async_session_factory() as session:
repo = UserRepository(session)
users = await repo.get_all()
return templates.TemplateResponse(
"users.html",
{"request": request, "users": users},
)
@router.get("/payments", response_class=HTMLResponse)
async def payments_page(request: Request):
async with async_session_factory() as session:
tariff_repo = TariffRepository(session)
stmt = select(Payment).order_by(Payment.created_at.desc())
result = await session.execute(stmt)
payments = list(result.scalars().all())
tariffs_map = {}
for p in payments:
if p.tariff_id not in tariffs_map:
t = await tariff_repo.get(p.tariff_id)
tariffs_map[p.tariff_id] = t.name if t else ""
return templates.TemplateResponse(
"payments.html",
{"request": request, "payments": payments, "tariffs_map": tariffs_map},
)
@router.get("/servers", response_class=HTMLResponse)
async def servers_page(request: Request):
async with async_session_factory() as session:
repo = ServerRepository(session)
servers = await repo.get_all()
return templates.TemplateResponse(
"servers.html",
{"request": request, "servers": servers},
)
@router.get("/tariffs", response_class=HTMLResponse)
async def tariffs_page(request: Request):
async with async_session_factory() as session:
repo = TariffRepository(session)
tariffs = await repo.get_all()
return templates.TemplateResponse(
"tariffs.html",
{"request": request, "tariffs": tariffs},
)