Spaces:
Paused
Paused
| from fastapi import FastAPI, Request | |
| from fastapi.responses import HTMLResponse, RedirectResponse | |
| import json | |
| from datetime import datetime | |
| app = FastAPI() | |
| # In-memory storage | |
| webhook_logs = [] | |
| async def webhook(request: Request): | |
| body = await request.body() | |
| try: | |
| body_json = json.loads(body) | |
| body_pretty = json.dumps(body_json, indent=2) | |
| except Exception: | |
| body_pretty = body.decode("utf-8", errors="ignore") | |
| webhook_logs.insert(0, { | |
| "time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"), | |
| "headers": dict(request.headers), | |
| "body": body_pretty | |
| }) | |
| del webhook_logs[50:] | |
| return {"status": "ok"} | |
| async def clear_logs(): | |
| webhook_logs.clear() | |
| return RedirectResponse("/", status_code=303) | |
| async def dashboard(): | |
| items = "" | |
| for i, log in enumerate(webhook_logs): | |
| items += f""" | |
| <div class="entry"> | |
| <div class="entry-header"> | |
| <span class="badge">#{i + 1}</span> | |
| <span class="time">{log["time"]}</span> | |
| </div> | |
| <details> | |
| <summary>Headers</summary> | |
| <pre>{json.dumps(log["headers"], indent=2)}</pre> | |
| </details> | |
| <details open> | |
| <summary>Body</summary> | |
| <pre>{log["body"]}</pre> | |
| </details> | |
| </div> | |
| """ | |
| html = f""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Webhook Viewer</title> | |
| <meta http-equiv="refresh" content="3"> | |
| <style> | |
| body {{ | |
| margin: 0; | |
| font-family: system-ui, -apple-system, BlinkMacSystemFont; | |
| background: #0b1020; | |
| color: #e5e7eb; | |
| }} | |
| header {{ | |
| position: sticky; | |
| top: 0; | |
| background: #020617; | |
| padding: 15px 20px; | |
| border-bottom: 1px solid #1e293b; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| }} | |
| header h1 {{ | |
| margin: 0; | |
| font-size: 20px; | |
| }} | |
| .controls {{ | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| }} | |
| .count {{ | |
| background: #1e293b; | |
| padding: 4px 10px; | |
| border-radius: 999px; | |
| font-size: 12px; | |
| }} | |
| button {{ | |
| background: #dc2626; | |
| border: none; | |
| color: white; | |
| padding: 6px 12px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 13px; | |
| }} | |
| button:hover {{ | |
| background: #b91c1c; | |
| }} | |
| main {{ | |
| padding: 20px; | |
| }} | |
| .entry {{ | |
| background: #020617; | |
| border: 1px solid #1e293b; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin-bottom: 16px; | |
| }} | |
| .entry-header {{ | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| margin-bottom: 10px; | |
| }} | |
| .badge {{ | |
| background: #2563eb; | |
| color: white; | |
| padding: 3px 8px; | |
| border-radius: 6px; | |
| font-size: 12px; | |
| }} | |
| .time {{ | |
| font-size: 12px; | |
| color: #94a3b8; | |
| }} | |
| details summary {{ | |
| cursor: pointer; | |
| font-weight: 600; | |
| margin: 8px 0; | |
| }} | |
| pre {{ | |
| background: #020617; | |
| border: 1px solid #1e293b; | |
| padding: 10px; | |
| border-radius: 6px; | |
| overflow-x: auto; | |
| color: #38bdf8; | |
| font-size: 13px; | |
| }} | |
| .empty {{ | |
| text-align: center; | |
| color: #64748b; | |
| margin-top: 50px; | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>📡 Webhook Viewer</h1> | |
| <div class="controls"> | |
| <span class="count">{len(webhook_logs)} requests</span> | |
| <form method="post" action="/clear"> | |
| <button type="submit">Clear</button> | |
| </form> | |
| </div> | |
| </header> | |
| <main> | |
| {items if items else "<p class='empty'>No webhooks received yet</p>"} | |
| </main> | |
| </body> | |
| </html> | |
| """ | |
| return html |