wrd_drishti / app /admin /router.py
devarshia5's picture
Upload 21 files
fe7cfe5 verified
Raw
History Blame Contribute Delete
8.19 kB
"""
Admin Panel β€” Built-in SQLite Browser
──────────────────────────────────────
Like pgAdmin but embedded in your FastAPI app.
Password protected. Runs on the same port (7860).
Features:
- Dashboard with DB health + stats
- Browse all tables
- View/edit/delete rows
- Run raw SQL queries
- Manage backups (create/restore/download)
- Integrity checks
Source inspiration:
- sqlite-web: https://github.com/coleifer/sqlite-web
- Flask-Admin: https://github.com/flask-admin/flask-admin
"""
from fastapi import APIRouter, Request, Depends, HTTPException, Form
from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
import sqlite3
import os
import secrets
from app.database import get_db, DB_PATH, integrity_check
from app.backup import (
create_snapshot, create_compressed_snapshot, create_vacuum_snapshot,
upload_to_hf_bucket, list_backups, restore_from_hf_bucket, diagnose_hf_setup
)
router = APIRouter(prefix="/admin", tags=["admin"])
templates = Jinja2Templates(directory="app/admin/templates")
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "change-me-in-production")
ADMIN_TOKEN = secrets.token_hex(32) # Session token
# ── Simple Auth ──
def verify_admin(request: Request):
token = request.cookies.get("admin_token")
if token != ADMIN_TOKEN:
raise HTTPException(status_code=303, headers={"Location": "/admin/login"})
return True
# ── LOGIN ──
@router.get("/login", response_class=HTMLResponse)
async def login_page(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
@router.post("/login")
async def login(request: Request, password: str = Form(...)):
if password == ADMIN_PASSWORD:
response = RedirectResponse(url="/admin/dashboard", status_code=303)
response.set_cookie("admin_token", ADMIN_TOKEN, httponly=True)
return response
return templates.TemplateResponse("login.html", {
"request": request, "error": "Wrong password"
})
# ── DASHBOARD ──
@router.get("/dashboard", response_class=HTMLResponse)
async def dashboard(request: Request, _=Depends(verify_admin)):
health = integrity_check()
conn = get_db()
# Get all tables
tables = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
).fetchall()
table_info = []
for table in tables:
name = table[0]
count = conn.execute(f"SELECT COUNT(*) FROM [{name}]").fetchone()[0]
table_info.append({"name": name, "row_count": count})
# Recent backup log
try:
backups = conn.execute(
"SELECT * FROM backup_log ORDER BY created_at DESC LIMIT 10"
).fetchall()
except:
backups = []
return templates.TemplateResponse("dashboard.html", {
"request": request,
"health": health,
"tables": table_info,
"backups": [dict(b) for b in backups],
"db_path": DB_PATH,
"db_exists": os.path.exists(DB_PATH),
})
# ── TABLE BROWSER ──
@router.get("/table/{table_name}", response_class=HTMLResponse)
async def view_table(request: Request, table_name: str, page: int = 1, _=Depends(verify_admin)):
conn = get_db()
per_page = 50
offset = (page - 1) * per_page
# Get columns
columns = conn.execute(f"PRAGMA table_info([{table_name}])").fetchall()
# Get rows
rows = conn.execute(
f"SELECT * FROM [{table_name}] LIMIT ? OFFSET ?",
(per_page, offset)
).fetchall()
total = conn.execute(f"SELECT COUNT(*) FROM [{table_name}]").fetchone()[0]
# Get indexes
indexes = conn.execute(f"PRAGMA index_list([{table_name}])").fetchall()
return templates.TemplateResponse("table_view.html", {
"request": request,
"table_name": table_name,
"columns": [dict(c) for c in columns],
"rows": [dict(r) for r in rows],
"total": total,
"page": page,
"per_page": per_page,
"total_pages": (total + per_page - 1) // per_page,
"indexes": [dict(i) for i in indexes],
})
# ── SQL QUERY RUNNER ──
@router.get("/query", response_class=HTMLResponse)
async def query_page(request: Request, _=Depends(verify_admin)):
return templates.TemplateResponse("query.html", {
"request": request, "results": None, "sql": ""
})
@router.post("/query", response_class=HTMLResponse)
async def run_query(request: Request, sql: str = Form(...), _=Depends(verify_admin)):
conn = get_db()
error = None
results = None
columns = []
try:
cursor = conn.execute(sql)
if sql.strip().upper().startswith("SELECT") or sql.strip().upper().startswith("PRAGMA"):
results = cursor.fetchall()
if results:
columns = results[0].keys()
else:
conn.commit()
results = [{"affected_rows": cursor.rowcount}]
columns = ["affected_rows"]
except Exception as e:
error = str(e)
return templates.TemplateResponse("query.html", {
"request": request,
"sql": sql,
"results": [dict(r) for r in results] if results else None,
"columns": columns,
"error": error,
})
# ── BACKUP MANAGEMENT ──
@router.get("/backups", response_class=HTMLResponse)
async def backups_page(request: Request, _=Depends(verify_admin)):
backup_info = list_backups()
health = integrity_check()
hf_diagnosis = diagnose_hf_setup()
return templates.TemplateResponse("backups.html", {
"request": request,
"backups": backup_info,
"health": health,
"hf_diagnosis": hf_diagnosis,
})
@router.post("/backups/create")
async def create_backup(request: Request, backup_type: str = Form("snapshot"), _=Depends(verify_admin)):
if backup_type == "snapshot":
result = create_snapshot(label="admin_manual")
elif backup_type == "compressed":
result = create_compressed_snapshot()
elif backup_type == "vacuum":
result = create_vacuum_snapshot()
elif backup_type == "hf_upload":
result = upload_to_hf_bucket()
else:
result = {"status": "error", "message": f"Unknown type: {backup_type}"}
# Store result in session/cookie for display
# For simplicity, we just redirect - the result is logged
return RedirectResponse(url="/admin/backups", status_code=303)
@router.get("/backups/download/{filename}")
async def download_backup(filename: str, _=Depends(verify_admin)):
filepath = os.path.join("/tmp/data/snapshots", filename)
if os.path.exists(filepath):
return FileResponse(filepath, filename=filename)
raise HTTPException(status_code=404, detail="Backup not found")
# ── API ENDPOINTS (for programmatic access) ──
@router.get("/api/health")
async def api_health():
"""Public health check β€” no auth required."""
return integrity_check()
@router.get("/api/tables")
async def api_tables(_=Depends(verify_admin)):
conn = get_db()
tables = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
).fetchall()
return [dict(t) for t in tables]
@router.get("/api/table/{table_name}/export")
async def export_table(table_name: str, format: str = "json", _=Depends(verify_admin)):
conn = get_db()
rows = conn.execute(f"SELECT * FROM [{table_name}]").fetchall()
return [dict(r) for r in rows]
# ── DIAGNOSTICS ──
@router.get("/api/diagnostics")
async def api_diagnostics(_=Depends(verify_admin)):
"""Run HF diagnostics - check credentials and repo access."""
return diagnose_hf_setup()
@router.post("/api/backup/upload")
async def api_backup_upload(_=Depends(verify_admin)):
"""Trigger HF bucket upload via API."""
return upload_to_hf_bucket()
@router.post("/api/backup/test")
async def api_backup_test(_=Depends(verify_admin)):
"""Test backup creation and return result."""
result = create_compressed_snapshot()
return result