From 1751cd3b4739f82d54d257e753a7fd0a0fbe138b Mon Sep 17 00:00:00 2001 From: Shroominic Date: Sat, 31 Jan 2026 07:23:58 +0800 Subject: [PATCH] remove deprecated htmx endpoints --- routstr/core/admin.py | 2218 ----------------------------------------- 1 file changed, 2218 deletions(-) diff --git a/routstr/core/admin.py b/routstr/core/admin.py index 390e834c..7198f553 100644 --- a/routstr/core/admin.py +++ b/routstr/core/admin.py @@ -4,7 +4,6 @@ from datetime import datetime, timezone from pathlib import Path from fastapi import APIRouter, Depends, HTTPException, Query, Request -from fastapi.responses import HTMLResponse, RedirectResponse from pydantic import BaseModel from sqlmodel import select @@ -41,109 +40,6 @@ def require_admin_api(request: Request) -> None: raise HTTPException(status_code=403, detail="Unauthorized") -def is_admin_authenticated(request: Request) -> bool: - auth_header = request.headers.get("Authorization") - if auth_header and auth_header.startswith("Bearer "): - token = auth_header.split(" ", 1)[1] - expiry = admin_sessions.get(token) - if expiry and expiry > int(datetime.now(timezone.utc).timestamp()): - return True - - return False - - -@admin_router.get( - "/partials/balances", - dependencies=[Depends(require_admin_api)], - response_class=HTMLResponse, -) -async def partial_balances(request: Request) -> str: - ( - balance_details, - total_wallet_balance_sats, - total_user_balance_sats, - owner_balance, - ) = await fetch_all_balances() - # Provide JSON for client usage - # Embed a script tag to update balanceDetails and the UI markup - rows = "".join( - [ - f"""
-
{detail["mint_url"].replace("https://", "").replace("http://", "")} • {detail["unit"].upper()}
-
{detail["wallet_balance"] if not detail.get("error") else "error"}
-
{detail["user_balance"] if not detail.get("error") else "-"}
-
0 else ""}">{detail["owner_balance"] if not detail.get("error") else "-"}
-
""" - for detail in balance_details - if detail.get("wallet_balance", 0) > 0 or detail.get("error") - ] - ) - return f""" -

Cashu Wallet Balance

-
- Your Balance (Total) - {owner_balance} sats -
-
- Total Wallet - {total_wallet_balance_sats} sats -
-
- User Balance - {total_user_balance_sats} sats -
-

Your balance = Total wallet - User balance

-
-
-
Mint / Unit
-
Wallet
-
Users
-
Owner
-
- {rows} -
- - """ - - -@admin_router.get( - "/partials/apikeys", - dependencies=[Depends(require_admin_api)], - response_class=HTMLResponse, -) -async def partial_apikeys(request: Request) -> str: - async with create_session() as session: - result = await session.exec(select(ApiKey)) - api_keys = result.all() - - def fmt_time(ts: int | None) -> str: - if ts is None: - return "" - dt = datetime.fromtimestamp(ts, tz=timezone.utc) - return f"{ts} ({dt.strftime('%Y-%m-%d %H:%M:%S')} UTC)" - - rows = "".join( - [ - f"{key.hashed_key}{'
(Child of ' + key.parent_key_hash[:8] + '...)' if key.parent_key_hash else ''}{key.balance}{key.total_spent}{key.total_requests}{key.refund_address}{fmt_time(key.key_expiry_time)}" - for key in api_keys - ] - ) - return f""" -

Temporary Balances

- - - - - - - - - - {rows} -
Hashed KeyBalance (mSats)Total Spent (mSats)Total RequestsRefund AddressRefund Time
- """ - - @admin_router.get("/api/temporary-balances", dependencies=[Depends(require_admin_api)]) async def get_temporary_balances_api(request: Request) -> list[dict[str, object]]: async with create_session() as session: @@ -304,612 +200,6 @@ class WithdrawRequest(BaseModel): unit: str = "sat" -def login_form() -> str: - return """ - - - - - - -
-

🔐 Admin Login

-
- - -
-
- - - """ - - -def setup_form() -> str: - return """ - - - - - - -
-

🔧 Initial Admin Setup

-

Create a secure password for your admin dashboard.

-
- - - -
-
-
- - - """ - - -def info(content: str) -> str: - return f""" - - - - - -
-

{content}

-
- - - """ - - -def admin_auth() -> str: - admin_pw = settings.admin_password - if admin_pw == "": - return setup_form() - else: - return login_form() - - -async def dashboard(request: Request) -> str: - return ( - f""" - - - - - """ - + """ - - - """ - + """ - -

Admin Dashboard

- -
-
Loading balances…
-
- - - - - - - - - - - - - -
- Withdrawal Token: -
- -

Save this token! It represents your withdrawn balance.

-
- -
-

Temporary Balances

-
Loading API keys…
-
- - - """ - ) - - -@admin_router.get("/", response_class=HTMLResponse) -async def admin(request: Request) -> RedirectResponse: - return RedirectResponse("/") - - -@admin_router.get("/logs/{request_id}", response_class=HTMLResponse) -async def view_logs(request: Request, request_id: str) -> str: - if not is_admin_authenticated(request): - return admin_auth() - - logger.info(f"Investigating logs for request_id: {request_id}") - - # Search for log entries with this request_id - log_entries = [] - logs_dir = Path("logs") - - if logs_dir.exists(): - # Get all log files sorted by modification time (most recent first) - log_files = sorted( - logs_dir.glob("*.log"), key=lambda x: x.stat().st_mtime, reverse=True - ) - - for log_file in log_files[:7]: # Check last 7 days of logs - try: - with open(log_file, "r") as f: - for line in f: - if request_id in line: - try: - # Parse JSON log entry - log_data = json.loads(line.strip()) - log_entries.append(log_data) - except json.JSONDecodeError: - # If not JSON, include raw line - log_entries.append({"raw": line.strip()}) - except Exception as e: - logger.error(f"Error reading log file {log_file}: {e}") - - # Sort entries by timestamp if available - log_entries.sort(key=lambda x: x.get("asctime", ""), reverse=False) - - # Format log entries for display - formatted_logs = [] - for entry in log_entries: - if "raw" in entry: - formatted_logs.append(f'
{entry["raw"]}
') - else: - # Format JSON log entry - timestamp = entry.get("asctime", "Unknown time") - level = entry.get("levelname", "INFO") - message = entry.get("message", "") - pathname = entry.get("pathname", "") - lineno = entry.get("lineno", "") - - # Extract additional fields - extra_fields = { - k: v - for k, v in entry.items() - if k - not in [ - "asctime", - "levelname", - "message", - "pathname", - "lineno", - "name", - "version", - "request_id", - ] - } - - level_class = level.lower() - formatted_entry = f""" -
-
- {timestamp} - [{level}] - {pathname}:{lineno} -
-
{message}
- """ - - if extra_fields: - formatted_entry += '
' - for key, value in extra_fields.items(): - formatted_entry += f'
{key}: {json.dumps(value) if isinstance(value, (dict, list)) else value}
' - formatted_entry += "
" - - formatted_entry += "
" - formatted_logs.append(formatted_entry) - - return ( - f""" - - - - - - """ - + f""" - - ← Back to Dashboard -

Log Investigation

-
- Request ID: {request_id} -
-
- {"".join(formatted_logs) if formatted_logs else '
No log entries found for this Request ID
'} -
-

- Found {len(log_entries)} log entries • Searched last 7 days of logs -

- - - """ - ) - - @admin_router.post("/withdraw", dependencies=[Depends(require_admin_api)]) async def withdraw( request: Request, withdraw_request: WithdrawRequest @@ -943,546 +233,6 @@ async def withdraw( return {"token": token} -DASHBOARD_MODELS_JS: str = """ - -""" - - -def models_page() -> str: - return ( - f""" - - - - {DASHBOARD_MODELS_JS} - - """ - + """ - - ← Back to Dashboard -

Models

- -
-

Models Table

-
- - -
- - - - - - - - - - -
ID
Loading…
-
- - -
-
- - - - - - - - - """ - ) - - class ModelCreate(BaseModel): id: str name: str @@ -1499,908 +249,6 @@ class ModelCreate(BaseModel): enabled: bool = True -@admin_router.get("/models", response_class=HTMLResponse) -async def admin_models(request: Request) -> str: - if is_admin_authenticated(request): - return models_page() - return admin_auth() - - -UPSTREAM_PROVIDERS_JS: str = """ - -""" - - -def upstream_providers_page() -> str: - return ( - f""" - - - - {UPSTREAM_PROVIDERS_JS} - - """ - + """ - - ← Back to Dashboard -

Upstream Providers

- -
-

Providers

-
- -
- - - - - - - - - - - - -
TypeBase URLStatusActions
Loading…
-
- - - - - - - - - - - """ - ) - - -@admin_router.get("/upstream-providers", response_class=HTMLResponse) -async def admin_upstream_providers(request: Request) -> str: - if is_admin_authenticated(request): - return upstream_providers_page() - return admin_auth() - - @admin_router.post( "/api/upstream-providers/{provider_id}/models", dependencies=[Depends(require_admin_api)], @@ -2953,72 +801,6 @@ async def get_openrouter_presets() -> list[dict[str, object]]: return models_data -DASHBOARD_CSS: str = """ -* { margin: 0; padding: 0; box-sizing: border-box; } -body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f7fa; color: #2c3e50; line-height: 1.6; padding: 2rem; } -h1, h2 { margin-bottom: 1rem; color: #1a202c; } -h1 { font-size: 2rem; } -h2 { font-size: 1.5rem; margin-top: 2rem; } -p { margin-bottom: 0.5rem; color: #4a5568; } -table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-top: 1rem; } -th { background: #4a5568; color: white; font-weight: 600; padding: 12px; text-align: left; } -td { padding: 12px; border-bottom: 1px solid #e2e8f0; } -tr:hover { background: #f7fafc; } -button { padding: 10px 20px; cursor: pointer; background: #4299e1; color: white; border: none; border-radius: 6px; font-weight: 600; margin-right: 10px; transition: all 0.2s; } -button:hover { background: #3182ce; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.1); } -button:disabled { background: #a0aec0; cursor: not-allowed; transform: none; } -.refresh-btn { background: #48bb78; } -.refresh-btn:hover { background: #38a169; } -.investigate-btn { background: #4299e1; } -.balance-card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem; } -.balance-item { display: flex; justify-content: space-between; margin-bottom: 1rem; } -.balance-label { color: #718096; } -.balance-value { font-size: 1.5rem; font-weight: 700; color: #2d3748; } -.balance-primary { color: #48bb78; } -.currency-grid { margin-top: 1rem; font-size: 0.9rem; } -.currency-row { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 0.5rem; padding: 0.4rem 0; border-bottom: 1px solid #f0f0f0; align-items: center; } -.currency-row:last-child { border-bottom: none; } -.currency-header { font-weight: 600; color: #4a5568; border-bottom: 2px solid #e2e8f0; padding-bottom: 0.5rem; } -.mint-name { color: #2d3748; font-size: 0.85rem; word-break: break-all; } -.balance-num { text-align: right; font-family: monospace; } -.owner-positive { color: #22c55e; } -.error-row { color: #dc2626; font-style: italic; } -#token-result { margin-top: 20px; padding: 20px; background: #e6fffa; border: 1px solid #38b2ac; border-radius: 8px; display: none; } -#token-text { font-family: 'Monaco', monospace; font-size: 13px; background: #2d3748; color: #68d391; padding: 15px; border-radius: 6px; margin: 10px 0; word-break: break-all; } -.copy-btn { background: #38a169; padding: 6px 12px; font-size: 14px; } -.copy-btn:hover { background: #2f855a; } -.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); backdrop-filter: blur(4px); } -.modal-content { background: white; margin: 5% auto; padding: 0.75rem 1rem 2.25rem; width: 90%; max-width: 720px; max-height: 85vh; overflow-y: auto; border-radius: 12px; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1); animation: slideIn 0.3s ease; } -@keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } -.close { color: #a0aec0; float: right; font-size: 28px; font-weight: bold; cursor: pointer; margin: -10px -10px 0 0; } -.close:hover { color: #2d3748; } -input[type="number"], input[type="text"], input[type="password"], select { width: 100%; padding: 10px; margin: 10px 0; border: 2px solid #e2e8f0; border-radius: 6px; font-size: 16px; transition: border 0.2s; } -input[type="number"]:focus, input[type="text"]:focus, input[type="password"]:focus, select:focus { outline: none; border-color: #4299e1; } -.warning { color: #e53e3e; font-weight: 600; margin: 10px 0; padding: 10px; background: #fff5f5; border-radius: 6px; } -""" - - -LOGS_CSS: str = """ -body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; } -h1 { color: #333; } -.back-btn { padding: 8px 16px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; margin-bottom: 20px; } -.back-btn:hover { background-color: #0056b3; } -.log-container { background-color: white; border: 1px solid #ddd; border-radius: 8px; padding: 20px; max-height: 80vh; overflow-y: auto; } -.log-entry { margin-bottom: 15px; padding: 10px; border: 1px solid #e0e0e0; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 12px; background-color: #f9f9f9; } -.log-entry.log-error { background-color: #fee; border-color: #fcc; } -.log-entry.log-warning { background-color: #ffc; border-color: #ff9; } -.log-entry.log-debug, .log-entry.log-trace { background-color: #f0f0f0; border-color: #ccc; } -.log-header { margin-bottom: 5px; color: #666; } -.log-timestamp { color: #0066cc; } -.log-level { font-weight: bold; } -.log-message { margin: 5px 0; color: #333; } -.log-extra { margin-top: 5px; padding-top: 5px; border-top: 1px solid #e0e0e0; } -.log-field { margin: 2px 0; color: #666; word-break: break-all; } -.no-logs { text-align: center; color: #666; padding: 40px; } -.request-id-display { background-color: #e9ecef; padding: 10px; border-radius: 4px; margin-bottom: 20px; font-family: monospace; } -""" - - @admin_router.get("/api/usage/metrics", dependencies=[Depends(require_admin_api)]) async def get_usage_metrics( request: Request,