Skip to content

Commit d815aec

Browse files
committed
Add basic server landing page at root
1 parent 107d54e commit d815aec

2 files changed

Lines changed: 203 additions & 10 deletions

File tree

src/blueapi/service/main.py

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import logging
23
import urllib.parse
34
from collections.abc import Awaitable, Callable
@@ -11,15 +12,17 @@
1112
Body,
1213
Depends,
1314
FastAPI,
15+
Form,
1416
HTTPException,
1517
Request,
1618
Response,
1719
status,
1820
)
1921
from fastapi.datastructures import Address
2022
from fastapi.middleware.cors import CORSMiddleware
21-
from fastapi.responses import RedirectResponse, StreamingResponse
23+
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
2224
from fastapi.security import OAuth2AuthorizationCodeBearer
25+
from fastapi.templating import Jinja2Templates
2326
from observability_utils.tracing import (
2427
add_span_attributes,
2528
get_tracer,
@@ -179,15 +182,6 @@ async def on_token_error_401(_: Request, __: Exception):
179182
)
180183

181184

182-
@secure_router.get("/", include_in_schema=False)
183-
def root_redirect() -> RedirectResponse:
184-
"""Redirect to docs url"""
185-
return RedirectResponse(
186-
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
187-
url=ApplicationConfig.DOCS_ENDPOINT,
188-
)
189-
190-
191185
@secure_router.get("/environment", tags=[Tag.ENV])
192186
@start_as_current_span(TRACER, "runner")
193187
def get_environment(
@@ -621,3 +615,67 @@ async def inject_propagated_observability_context(
621615
attach(ctx)
622616
response = await call_next(request)
623617
return response
618+
619+
620+
templates = Jinja2Templates(directory="templates")
621+
622+
623+
@secure_router.get("/", include_in_schema=False, response_class=HTMLResponse)
624+
def root_landing(
625+
request: Request,
626+
runner: Annotated[WorkerDispatcher, Depends(_runner)],
627+
) -> HTMLResponse:
628+
629+
if runner._config.env.metadata:
630+
instrument = runner._config.env.metadata.instrument
631+
else:
632+
instrument = "<ixx>"
633+
634+
devices = runner.run(interface.get_devices)
635+
devices = [
636+
{"device": device.name, "protocols": [p.name for p in device.protocols]}
637+
for device in devices
638+
]
639+
640+
plans = runner.run(interface.get_plans)
641+
task_list = get_tasks(runner)
642+
643+
context = {
644+
"instrument": instrument,
645+
"devices": devices,
646+
"plans": plans,
647+
"tasks": task_list.tasks,
648+
}
649+
650+
return templates.TemplateResponse(
651+
request=request, name="index.html", context=context
652+
)
653+
654+
655+
@secure_router.post("/run", include_in_schema=True, tags=[Tag.TASK])
656+
@start_as_current_span(TRACER)
657+
def run(
658+
name: Annotated[str, Form()],
659+
params: Annotated[str, Form()],
660+
instrument_session: Annotated[str, Form()],
661+
request: Request,
662+
response: Response,
663+
runner: Annotated[WorkerDispatcher, Depends(_runner)],
664+
) -> RedirectResponse:
665+
666+
task_request = TaskRequest(
667+
name=name,
668+
params=json.loads(params), # do this validator in the model?
669+
instrument_session=instrument_session,
670+
)
671+
res = submit_task(request, response, task_request, runner)
672+
673+
tid = res.task_id
674+
req_task = WorkerTask(task_id=tid)
675+
676+
try:
677+
set_active_task(request, req_task, runner)
678+
except HTTPException:
679+
delete_submitted_task(tid, runner)
680+
681+
return RedirectResponse(status_code=status.HTTP_204_NO_CONTENT, url="/")

templates/index.html

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>{{instrument}}-blueapi</title>
7+
<style>
8+
dl {
9+
padding-left: 30px;
10+
}
11+
.container {
12+
border: 2px solid rgb(75 70 74);
13+
border-radius: 0.5em;
14+
padding: 20px 10px;
15+
font: sans-serif;
16+
17+
display: flex;
18+
}
19+
.container > * {
20+
padding: 10px;
21+
border: 2px solid rgb(95 97 110);
22+
border-radius: 0.5em;
23+
24+
margin: 0 10px;
25+
flex: 1 1 200px;
26+
}
27+
</style>
28+
</head>
29+
<body>
30+
<h1>{{instrument}}-blueapi</h1>
31+
<p>
32+
api docs available at
33+
<a href="/docs">/docs</a>.
34+
</p>
35+
36+
<div class="container">
37+
38+
<div>
39+
<h2>Run Plan</h2>
40+
<form action="/run" method="POST" id="run_form">
41+
<div>
42+
<label for="instrument_session">Instrument Session</label>
43+
<input type="text" id="instrument_session" name="instrument_session">
44+
</div>
45+
46+
<div>
47+
<label for="name">Select Plan</label>
48+
<select id="name" name="name", form="run_form">
49+
<option value="">--select a plan--</option>
50+
{% for plan in plans %}
51+
<option value="{{plan.name}}">{{plan.name}}</option>
52+
{% endfor %}
53+
</select>
54+
</div>
55+
56+
<div>
57+
<label for="params">Plan Parameters</label>
58+
<input type="text" id="params" name="params">
59+
</div>
60+
61+
<input type="submit" value="run">
62+
</form>
63+
64+
<h2>Tasks</h2>
65+
66+
<h3>Current task:</h3>
67+
{% for task in tasks if not task.is_pending and not task.is_complete %}
68+
<dl>
69+
<dt> {{task.task_id}}
70+
<ul>
71+
<li>{{task.task.name}}</li>
72+
<li>{{task.task.params}}</li>
73+
</ul>
74+
</dt>
75+
</dl>
76+
{% endfor %}
77+
78+
<h3>Pending tasks:</h3>
79+
{% for task in tasks if task.is_pending %}
80+
<dl>
81+
<dt> {{task.task_id}}
82+
<ul>
83+
<li>{{task.task.name}}</li>
84+
<li>{{task.task.params}}</li>
85+
</ul>
86+
</dt>
87+
</dl>
88+
{% endfor %}
89+
90+
<h3>Completed tasks:</h3>
91+
{% for task in tasks if task.is_complete%}
92+
<dl>
93+
<dt> {{task.task_id}}
94+
<ul>
95+
<li>{{task.task.name}}</li>
96+
<li>{{task.task.params}}</li>
97+
<li>outcome: {{task.outcome.outcome}} </li>
98+
<li>result: {{task.outcome.result}}</li>
99+
</ul>
100+
</dt>
101+
</dl>
102+
{% endfor %}
103+
104+
</div>
105+
106+
<div>
107+
<h2>Plans</h2>
108+
{% for plan in plans %}
109+
<h3>{{plan.name}}</h3>
110+
<dl>
111+
<dt>Description:</dt>
112+
<dd>{{plan.description}}</dd>
113+
<dt> Plan Parameters:</dt>
114+
{% for p in plan.parameter_schema.properties %}
115+
<dd>{{ p }}</dd>
116+
{% endfor %}
117+
</dl>
118+
{% endfor %}
119+
</div>
120+
121+
<div>
122+
<h2>Devices</h2>
123+
{% for device in devices %}
124+
<h3> {{device.device}}</h3>
125+
<dl>
126+
<dt>Protocols:</dt>
127+
<dd>{{device.protocols}}</dd>
128+
</dl>
129+
{% endfor %}
130+
</div>
131+
132+
</div>
133+
134+
</body>
135+
</html>

0 commit comments

Comments
 (0)