UI Improvements (#7)

* Initial version with UI templates

* Implement basic screens

* Linting & bump to 0.3.0

* Tick off some TODOs
This commit is contained in:
Christiaan Goossens
2024-12-27 16:52:32 +01:00
committed by GitHub
parent 597d9cdf7d
commit 0d61861343
16 changed files with 215 additions and 34 deletions

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" class="h-full min-h-full max-h-full">
<head>
{% block head %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
{% endblock %}
</head>
<body class="bg-gray-200 flex items-center justify-center h-full">
<div class="bg-white p-6 rounded-lg shadow-lg max-w-md">
{% block content %}{% endblock %}
</div>
</body>
</html>

View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}Oops!{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block content %}
<div class="text-center">
<h1 class="text-2xl font-bold mb-4">Login failed.</h1>
<p class="mb-4">{{ error }}</p>
<div class="my-6">
<a href='{{ link }}'
class="w-full py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">Try
again</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}Logged in!{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block content %}
<div class="text-center">
<div class="my-6">
<h2 class="text-xl font-semibold mb-6 text-gray-800">I want to login to this browser</h2>
<a href='{{ link }}'
class="w-full py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">Click
here to login automatically</a>
</div>
<hr class="my-12">
<div class="my-6">
<h2 class="text-xl font-semibold mb-4 text-gray-800">I am on a mobile device</h2>
<p class="mb-4">Your one-time code is: <b class="text-blue-600 text-xl">{{ code }}</b></p>
<p class="mb-4 text-sm">You have 5 minutes to use this code on any device.<br />The code can only
be used once.</p>
<p class="mb-4 text-sm">Please type the code into your app manually. If you don't see a code input, select
'Login with
OpenID Connect (SSO)' first.</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,60 @@
"""Jinja2 Async Environment"""
import logging
from os import path
from typing import Dict, Any
from jinja2 import Environment, DictLoader
from aiofiles.os import scandir as async_scandir
from aiofiles import open as async_open
_LOGGER = logging.getLogger(__name__)
templates: Dict[str, str] = {}
class AsyncTemplateRenderer:
"""An asynchronous template renderer that caches rendered templates."""
def __init__(self, template_dir: str = None):
self.template_dir = template_dir or path.dirname(path.abspath(__file__))
async def fetch_templates(self) -> None:
"""Fetches all HTML files from the template directory."""
templates.clear()
files = await async_scandir(self.template_dir)
for file in files:
if file.is_dir():
continue
filename = file.name
if filename.endswith(".html"):
template_path = path.join(self.template_dir, filename)
try:
_LOGGER.debug("Fetching template %s from disk", filename)
async with async_open(
template_path, mode="r", encoding="utf-8"
) as f:
content = await f.read()
templates[filename] = content
except (OSError, IOError) as e:
_LOGGER.warning("Error reading template file %s: %s", filename, e)
async def render_template(self, template_name: str, **kwargs: Any) -> str:
"""Renders a template with the given parameters."""
if not templates:
await (
self.fetch_templates()
) # If the templates haven't been fetched, fetch them
if template_name not in templates:
raise ValueError(f"Template '{template_name}' not found.")
env = Environment(loader=DictLoader(templates), enable_async=True)
template = env.get_template(template_name)
# Render template
rendered_output = await template.render_async(**kwargs)
return rendered_output

View File

@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}OIDC Login{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block content %}
<div class="text-center">
<h1 class="text-2xl font-bold mb-4">Home Assistant</h1>
<p class="mb-4">You have been invited to login to Home Assistant.<br />Start the login process below.</p>
<button id="oidc-login-btn"
class="w-full py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">
Login with OpenID Connect (SSO)
</button>
<p class="mt-6 text-sm">After login, you will be granted a one-time code to login to any device. You may complete
this login on your desktop or any mobile browser and then use the token for any desktop or the Home Assistant
app.</p>
</div>
<script>
document.getElementById('oidc-login-btn').addEventListener('click', function () {
this.innerHTML = 'Redirecting...';
this.disabled = true;
this.classList.add('bg-gray-500');
window.location.href = '/auth/oidc/redirect';
});
</script>
{% endblock %}