Add friendly crash page
This commit is contained in:
4 files changed
+100
-1
No files matched your search
+18
-1
@@ -3,7 +3,10 @@ from __future__ import annotations
|
||||
|
||||
import click
|
||||
import os
|
||||
from flask import Flask, render_template
|
||||
from uuid import uuid4
|
||||
|
||||
from flask import Flask, render_template, request
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from boker.auth import current_user_id, is_logged_in, is_site_admin, normalize_email
|
||||
from boker.config import DEFAULT_SECRET_KEY, Config, ProductionConfig
|
||||
@@ -64,6 +67,20 @@ def create_app(config_overrides: dict | None = None) -> Flask:
|
||||
def forbidden(e):
|
||||
return render_template("403.html"), 403
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def unexpected_error(error):
|
||||
if isinstance(error, HTTPException):
|
||||
return error
|
||||
|
||||
crash_id = uuid4().hex[:12]
|
||||
app.logger.exception(
|
||||
"Unhandled exception [%s] during %s %s",
|
||||
crash_id,
|
||||
request.method,
|
||||
request.path,
|
||||
)
|
||||
return render_template("500.html", crash_id=crash_id), 500
|
||||
|
||||
@app.cli.command("init-db")
|
||||
def init_db_command() -> None:
|
||||
if not database_extensions_available() or db is None:
|
||||
|
||||
@@ -5925,6 +5925,9 @@ select.control { cursor: pointer; }
|
||||
.error-pg--403 .error-pg__suit {
|
||||
color: rgba(155,140,240,.06);
|
||||
}
|
||||
.error-pg--500 .error-pg__suit {
|
||||
color: rgba(224,177,92,.07);
|
||||
}
|
||||
|
||||
.error-pg__body {
|
||||
position: relative;
|
||||
@@ -5971,6 +5974,18 @@ select.control { cursor: pointer; }
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-pg__trace {
|
||||
margin: -12px 0 28px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
color: var(--faint);
|
||||
background: var(--panel);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
LANDING — hero kicker (replaces pill eyebrow in hero only)
|
||||
================================================================ */
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}500 · Something Went Wrong · myboker.org{% endblock %}
|
||||
{% block page_class %}page--error{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="error-pg error-pg--500">
|
||||
<div class="error-pg__suit" aria-hidden="true">♦</div>
|
||||
<div class="error-pg__body">
|
||||
<span class="error-pg__code">500</span>
|
||||
<h1 class="error-pg__title">Something went wrong.</h1>
|
||||
<p class="error-pg__sub">The app hit an unexpected error. Try again in a minute, or contact support if it keeps happening.</p>
|
||||
<p class="error-pg__trace">Incident {{ crash_id }}</p>
|
||||
<div class="error-pg__actions">
|
||||
<a class="btn btn--primary btn--lg" href="{{ url_for('public.home') }}">Go home →</a>
|
||||
<a class="btn btn--ghost btn--lg" href="{{ url_for('public.help') }}#contact">Contact support</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,47 @@
|
||||
import re
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from app import create_app
|
||||
from boker.db import db
|
||||
|
||||
|
||||
class ErrorPageTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmpdir = tempfile.TemporaryDirectory()
|
||||
db_path = Path(self.tmpdir.name) / "test.sqlite3"
|
||||
self.app = create_app(
|
||||
{
|
||||
"TESTING": True,
|
||||
"PROPAGATE_EXCEPTIONS": False,
|
||||
"SQLALCHEMY_DATABASE_URI": f"sqlite:///{db_path}",
|
||||
"WTF_CSRF_ENABLED": False,
|
||||
}
|
||||
)
|
||||
|
||||
@self.app.get("/explode")
|
||||
def explode():
|
||||
raise RuntimeError("boom")
|
||||
|
||||
self.client = self.app.test_client()
|
||||
|
||||
def tearDown(self):
|
||||
with self.app.app_context():
|
||||
db.session.remove()
|
||||
db.drop_all()
|
||||
db.engine.dispose()
|
||||
self.tmpdir.cleanup()
|
||||
|
||||
def test_unexpected_errors_render_crash_page_with_incident_id(self):
|
||||
response = self.client.get("/explode")
|
||||
|
||||
self.assertEqual(response.status_code, 500)
|
||||
self.assertIn(b"Something went wrong.", response.data)
|
||||
self.assertIn(b"Incident", response.data)
|
||||
self.assertNotIn(b"RuntimeError", response.data)
|
||||
self.assertRegex(response.get_data(as_text=True), re.compile(r"Incident [a-f0-9]{12}"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user