Add friendly crash page

This commit is contained in:
SowinskiBraeden committed 2026-06-28 17:47:58 -07:00
1 parent e376b3628a
commit adbc60bfc3
4 files changed
+100 -1

No files matched your search

+18 -1
View File
@@ -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:
+15
View File
@@ -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)
================================================================ */
+20
View File
@@ -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 %}
+47
View File
@@ -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()