Skip to content

Commit f6bdd99

Browse files
authored
Merge pull request #245 from labthings/pretty-fallback
Pretty fallback
2 parents ced9008 + dc56665 commit f6bdd99

5 files changed

Lines changed: 378 additions & 109 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ build-backend = "hatchling.build"
5656
include = [
5757
"src"
5858
]
59-
artifacts = ["src/*.json"]
59+
artifacts = ["src/*.json", "src/*.jinja"]
6060

6161
[tool.hatch.build.targets.wheel]
62-
artifacts = ["src/*.json"]
62+
artifacts = ["src/*.json", "src/*.jinja"]
6363

6464
[tool.pytest.ini_options]
6565
addopts = [

src/labthings_fastapi/server/cli.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
from argparse import ArgumentParser, Namespace
2222
import sys
23-
from typing import Optional
23+
from typing import Literal, Optional, overload
2424

2525
from pydantic import ValidationError
2626
import uvicorn
@@ -108,6 +108,16 @@ def config_from_args(args: Namespace) -> ThingServerConfig:
108108
raise RuntimeError("No configuration (or empty configuration) provided")
109109

110110

111+
@overload
112+
def serve_from_cli(
113+
argv: Optional[list[str]], dry_run: Literal[True]
114+
) -> ThingServer: ...
115+
116+
117+
@overload
118+
def serve_from_cli(argv: Optional[list[str]], dry_run: Literal[False]) -> None: ...
119+
120+
111121
def serve_from_cli(
112122
argv: Optional[list[str]] = None, dry_run: bool = False
113123
) -> ThingServer | None:
@@ -150,10 +160,14 @@ def serve_from_cli(
150160
if args.fallback:
151161
print(f"Error: {e}")
152162
print("Starting fallback server.")
163+
153164
app = fallback.app
154-
app.labthings_config = config
155-
app.labthings_server = server
156-
app.labthings_error = e
165+
app.set_context(
166+
fallback.FallbackContext(
167+
config=config, server=server, error=e, log_history=None
168+
)
169+
)
170+
157171
uvicorn.run(app, host=args.host, port=args.port)
158172
else:
159173
if isinstance(e, (ValidationError, ThingImportFailure)):
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>LabThings — Server Failed to Start</title>
6+
<meta name="description" content="LabThings failed to start due to a configuration or runtime error. Diagnostic information is shown below." />
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
9+
<style>
10+
:root {
11+
12+
/* UI colours */
13+
--bg: #f8fafc;
14+
--panel: #ffffff;
15+
--text: #1f2933;
16+
--muted: #6b7280;
17+
--border: #e5e7eb;
18+
--code-bg: #f1f5f9;
19+
--error-bg: #fee2e2;
20+
--error-border: #fecaca;
21+
--error-text: #7f1d1d;
22+
}
23+
24+
* {
25+
box-sizing: border-box;
26+
}
27+
28+
body {
29+
margin: 0;
30+
padding: 2rem;
31+
font-family: system-ui, -apple-system, BlinkMacSystemFont,
32+
"Segoe UI", Roboto, Helvetica, Arial, sans-serif;
33+
background: var(--bg);
34+
color: var(--text);
35+
line-height: 1.5;
36+
}
37+
38+
.container {
39+
max-width: 900px;
40+
margin: 0 auto;
41+
}
42+
43+
.panel {
44+
background: var(--panel);
45+
border: 1px solid var(--border);
46+
border-radius: 8px;
47+
padding: 2rem;
48+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
49+
}
50+
51+
.header {
52+
display: flex;
53+
align-items: center;
54+
gap: 1rem;
55+
margin-bottom: 1.5rem;
56+
}
57+
58+
.logo {
59+
width: 48px;
60+
height: 48px;
61+
flex-shrink: 0;
62+
}
63+
64+
h1 {
65+
margin: 0;
66+
font-size: 1.75rem;
67+
}
68+
69+
h2 {
70+
margin-top: 2rem;
71+
font-size: 1.25rem;
72+
border-bottom: 1px solid var(--border);
73+
padding-bottom: 0.25rem;
74+
}
75+
76+
p {
77+
margin: 0.5rem 0 1rem;
78+
}
79+
80+
.muted {
81+
color: var(--muted);
82+
}
83+
84+
.error-message {
85+
padding: 1rem;
86+
background: var(--error-bg);
87+
border: 1px solid var(--error-border);
88+
border-radius: 6px;
89+
color: var(--error-text);
90+
margin: 1.5rem 0;
91+
font-weight: 500;
92+
}
93+
94+
ul {
95+
padding-left: 1.25rem;
96+
}
97+
98+
li {
99+
margin: 0.25rem 0;
100+
}
101+
102+
pre {
103+
background: var(--code-bg);
104+
border: 1px solid var(--border);
105+
border-radius: 6px;
106+
padding: 1rem;
107+
overflow-x: auto;
108+
white-space: pre-wrap;
109+
word-break: break-word;
110+
font-size: 0.875rem;
111+
}
112+
113+
footer {
114+
margin-top: 2rem;
115+
text-align: center;
116+
font-size: 0.8rem;
117+
color: var(--muted);
118+
}
119+
</style>
120+
</head>
121+
122+
<body>
123+
<main class="container">
124+
<section class="panel">
125+
126+
<div class="header">
127+
<!-- Inline LabThings logo -->
128+
<svg class="logo" height="100%" viewBox="0 0 114 114" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g><path d="M85.039,113.386l28.347,0l0,-113.386l-70.866,0l0,28.346l42.519,0l0,85.04Z" style="fill:url(#_Linear1);"/><path d="M0,8.504l0,-8.504l28.346,0l0,85.039l42.52,0l0,28.347l-70.866,0l0,-8.504l14.173,0l0,-5.669l-14.173,0l0,-5.67l5.578,0l0,-5.669l-5.578,0l0,-5.669l14.173,0l0,-5.67l-14.173,0l0,-5.669l5.578,0l0,-5.669l-5.578,0l0,-5.669l14.173,0l0,-5.67l-14.173,0l0,-5.669l5.578,0l0,-5.669l-5.578,0l0,-5.67l14.173,0l0,-5.669l-14.173,0l0,-5.669l5.578,0l0,-5.669l-5.578,0l0,-5.67l14.173,0l0,-5.669l-14.173,0Z" style="fill:url(#_Linear2);"/></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(113.386,113.386,-113.386,113.386,0,0)"><stop offset="0" style="stop-color:#00d677;stop-opacity:1"/><stop offset="1" style="stop-color:#009855;stop-opacity:1"/></linearGradient><linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(113.386,113.386,-113.386,113.386,-5.32724,10.5931)"><stop offset="0" style="stop-color:#a535ff;stop-opacity:1"/><stop offset="1" style="stop-color:#57009b;stop-opacity:1"/></linearGradient></defs></svg>
129+
130+
<h1>LabThings couldn't start</h1>
131+
</div>
132+
133+
<p class="muted">
134+
The LabThings server failed during startup. This is usually caused by
135+
an invalid configuration, missing dependency, or a runtime exception.
136+
</p>
137+
138+
{% if error_message %}
139+
<div class="error-message">
140+
{{ error_message }}
141+
</div>
142+
{% endif %}
143+
144+
{% if things %}
145+
<h2>The following Things loaded successfully:</h2>
146+
<ul>
147+
{% for name in things %}
148+
<li>{{ name }}</li>
149+
{% endfor %}
150+
</ul>
151+
{% endif %}
152+
153+
<h2>Configuration</h2>
154+
<pre>{{ config }}</pre>
155+
156+
<h2>Traceback</h2>
157+
<pre>{{ traceback }}</pre>
158+
159+
<h2>Logging</h2>
160+
{% if logginginfo %}
161+
<pre>{{ logginginfo }}</pre>
162+
{% else %}
163+
<p class="muted">No logging information available.</p>
164+
{% endif %}
165+
</section>
166+
167+
<footer>
168+
LabThings fallback server
169+
</footer>
170+
</main>
171+
</body>
172+
</html>

0 commit comments

Comments
 (0)