Add websites page to admin sidebar
This commit is contained in:
103
app/admin_web.py
103
app/admin_web.py
@@ -15,6 +15,7 @@ from typing import Any
|
||||
import aioredis
|
||||
from fastapi import APIRouter, Depends, Form, Request
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from starlette.responses import HTMLResponse, RedirectResponse
|
||||
from starlette.status import HTTP_303_SEE_OTHER, HTTP_401_UNAUTHORIZED
|
||||
@@ -172,6 +173,7 @@ def _render_admin_page(title: str, page_title: str, body: str) -> HTMLResponse:
|
||||
<aside class="sidebar">
|
||||
<h1>IRT Bank Soal Admin</h1>
|
||||
<a href="/admin/dashboard">Dashboard</a>
|
||||
<a href="/admin/websites">Websites</a>
|
||||
<a href="/admin/calibration-status">Calibration Status</a>
|
||||
<a href="/admin/item-statistics">Item Statistics</a>
|
||||
<a href="/admin/session-overview">Session Overview</a>
|
||||
@@ -209,6 +211,34 @@ def _truncate(text: str | None, max_length: int = 120) -> str:
|
||||
return f"{text[: max_length - 3]}..."
|
||||
|
||||
|
||||
def _websites_form_body(
|
||||
websites: list[Website],
|
||||
error: str | None = None,
|
||||
success: str | None = None,
|
||||
site_name: str = "",
|
||||
site_url: str = "",
|
||||
) -> str:
|
||||
error_html = f'<div class="error">{escape(error)}</div>' if error else ""
|
||||
success_html = f'<div class="success">{escape(success)}</div>' if success else ""
|
||||
rows = [[website.id, website.site_name, website.site_url] for website in websites]
|
||||
websites_table = _table(["ID", "Name", "URL"], rows)
|
||||
return f"""
|
||||
<p class="muted">Register websites here so imports and tryout references can be tied to a known source site.</p>
|
||||
{success_html}
|
||||
{error_html}
|
||||
<form method="post" action="/admin/websites" autocomplete="off">
|
||||
<label for="site_name">Website Name</label>
|
||||
<input id="site_name" name="site_name" type="text" value="{escape(site_name)}" placeholder="Sejoli Demo Site">
|
||||
<label for="site_url">Website URL</label>
|
||||
<input id="site_url" name="site_url" type="url" value="{escape(site_url)}" placeholder="https://example.com">
|
||||
<button type="submit">Add Website</button>
|
||||
</form>
|
||||
<h3 style="margin-top:24px">Registered Websites</h3>
|
||||
<p class="muted">Use the website ID when importing read-only tryout snapshots.</p>
|
||||
{websites_table}
|
||||
"""
|
||||
|
||||
|
||||
async def _basis_items_for_playground(db: AsyncSession, limit: int = 20) -> list[Item]:
|
||||
result = await db.execute(
|
||||
select(Item)
|
||||
@@ -459,6 +489,79 @@ async def dashboard_view(request: Request, db: AsyncSession = Depends(get_db)):
|
||||
return _render_admin_page("IRT Bank Soal Admin", "Dashboard", body)
|
||||
|
||||
|
||||
@router.get("/websites", include_in_schema=False)
|
||||
async def websites_view(request: Request, db: AsyncSession = Depends(get_db)):
|
||||
admin = await _current_admin(request)
|
||||
if not admin:
|
||||
return _login_redirect()
|
||||
|
||||
result = await db.execute(select(Website).order_by(Website.id.asc()))
|
||||
websites = list(result.scalars().all())
|
||||
body = _websites_form_body(websites)
|
||||
return _render_admin_page("Websites", "Websites", body)
|
||||
|
||||
|
||||
@router.post("/websites", include_in_schema=False)
|
||||
async def websites_submit(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
site_name: str = Form(...),
|
||||
site_url: str = Form(...),
|
||||
):
|
||||
admin = await _current_admin(request)
|
||||
if not admin:
|
||||
return _login_redirect()
|
||||
|
||||
normalized_name = site_name.strip()
|
||||
normalized_url = site_url.strip().rstrip("/")
|
||||
|
||||
if not normalized_name:
|
||||
result = await db.execute(select(Website).order_by(Website.id.asc()))
|
||||
websites = list(result.scalars().all())
|
||||
body = _websites_form_body(
|
||||
websites,
|
||||
error="Website name is required.",
|
||||
site_name=site_name,
|
||||
site_url=site_url,
|
||||
)
|
||||
return _render_admin_page("Websites", "Websites", body)
|
||||
|
||||
if not normalized_url.startswith(("http://", "https://")):
|
||||
result = await db.execute(select(Website).order_by(Website.id.asc()))
|
||||
websites = list(result.scalars().all())
|
||||
body = _websites_form_body(
|
||||
websites,
|
||||
error="Website URL must start with http:// or https://.",
|
||||
site_name=site_name,
|
||||
site_url=site_url,
|
||||
)
|
||||
return _render_admin_page("Websites", "Websites", body)
|
||||
|
||||
website = Website(site_name=normalized_name, site_url=normalized_url)
|
||||
db.add(website)
|
||||
try:
|
||||
await db.commit()
|
||||
except IntegrityError:
|
||||
await db.rollback()
|
||||
result = await db.execute(select(Website).order_by(Website.id.asc()))
|
||||
websites = list(result.scalars().all())
|
||||
body = _websites_form_body(
|
||||
websites,
|
||||
error="Website URL already exists.",
|
||||
site_name=site_name,
|
||||
site_url=site_url,
|
||||
)
|
||||
return _render_admin_page("Websites", "Websites", body)
|
||||
|
||||
result = await db.execute(select(Website).order_by(Website.id.asc()))
|
||||
websites = list(result.scalars().all())
|
||||
body = _websites_form_body(
|
||||
websites,
|
||||
success=f"Website added successfully with ID {website.id}.",
|
||||
)
|
||||
return _render_admin_page("Websites", "Websites", body)
|
||||
|
||||
|
||||
@router.get("/calibration-status", include_in_schema=False)
|
||||
async def calibration_status_view(request: Request, db: AsyncSession = Depends(get_db)):
|
||||
admin = await _current_admin(request)
|
||||
|
||||
Reference in New Issue
Block a user