Style login page

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2026-01-18 13:18:35 +01:00
parent a1474dab43
commit 79305724f2
6 changed files with 85 additions and 49 deletions

View file

@ -2,7 +2,9 @@ use std::sync::LazyLock;
use std::time::Duration;
use axum::Router;
use axum::http::StatusCode;
use axum::response::Html;
use axum::response::IntoResponse;
use axum::routing::get;
use axum_login::AuthManagerLayerBuilder;
use axum_login::AuthnBackend;
@ -16,7 +18,6 @@ use sqlx::SqlitePool;
use tera::Context;
use tera::Tera;
use thiserror::Error;
use tokio::sync::RwLock;
use tokio::task;
use tokio::task::AbortHandle;
use tower_livereload::LiveReloadLayer;
@ -28,6 +29,29 @@ use tracing_subscriber::EnvFilter;
pub mod users;
pub type WebResult<T> = Result<T, AppError>;
#[derive(Debug, Error, Display)]
pub enum AppError {
/// An error occurred while templating
Tera(#[from] tera::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let mut error_context = Context::new();
error_context.insert("error", &self.to_string());
(
StatusCode::INTERNAL_SERVER_ERROR,
Html(
render_template("internal_error.tera.html", &error_context)
.unwrap_or_else(|_| "ERROR RENDERING ERROR! FATAL".to_string()),
),
)
.into_response()
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
run().await
@ -93,7 +117,7 @@ pub struct AppState {
pub db: SqlitePool,
}
pub static TERA: LazyLock<RwLock<Tera>> =
pub static TERA: LazyLock<std::sync::RwLock<Tera>> =
LazyLock::new(|| Tera::new("templates/**.tera.html").unwrap().into());
async fn run() -> anyhow::Result<()> {
@ -140,7 +164,7 @@ async fn run() -> anyhow::Result<()> {
.iter()
.any(|ev| !matches!(ev.event.kind, EventKind::Access(_)))
{
match TERA.blocking_write().full_reload() {
match TERA.write().unwrap().full_reload() {
Ok(()) => {
reloader.reload();
}
@ -192,14 +216,10 @@ async fn shutdown_signal(handle: AbortHandle) {
handle.abort();
}
pub async fn render_template(name: &str, context: &Context) -> tera::Result<String> {
TERA.read().await.render(name, context)
pub fn render_template(name: &str, context: &Context) -> tera::Result<String> {
TERA.read().unwrap().render(name, context)
}
async fn show_protected() -> Html<String> {
Html(
render_template("protected.tera.html", &Context::default())
.await
.unwrap(),
)
Html(render_template("protected.tera.html", &Context::default()).unwrap())
}

View file

@ -3,26 +3,15 @@ use axum::http::StatusCode;
use axum::response::Html;
use axum::response::IntoResponse;
use axum::response::Redirect;
use tera::Context;
use crate::AuthSession;
use crate::UserCredentials;
use crate::WebResult;
use crate::render_template;
pub async fn show_login() -> Html<String> {
format!(
r##"
<!DOCTYPE html>
<html>
<body>
<form action="/login" method="POST">
<input type="text" name="username"/>
<input type="password" name="password"/>
<input type="submit" />
</form>
</body>
</html>
"##
)
.into()
pub async fn show_login() -> WebResult<Html<String>> {
Ok(render_template("users/login.tera.html", &Context::default()).map(Html)?)
}
pub async fn do_login(

View file

@ -4,26 +4,15 @@ use axum::response::Html;
use axum::response::IntoResponse;
use axum::response::Redirect;
use password_auth::generate_hash;
use tera::Context;
use crate::AppState;
use crate::UserCredentials;
use crate::WebResult;
use crate::render_template;
pub async fn show_register() -> Html<String> {
format!(
r##"
<!DOCTYPE html>
<html>
<body>
<form action="/register" method="POST">
<input type="text" name="username"/>
<input type="password" name="password"/>
<input type="submit" />
</form>
</body>
</html>
"##
)
.into()
pub async fn show_register() -> WebResult<Html<String>> {
Ok(render_template("users/register.tera.html", &Context::default()).map(Html)?)
}
pub async fn do_register(

View file

@ -11,20 +11,24 @@
</head>
<body class="min-h-screen flex flex-col">
<nav class="bg-orange-300 px-2 py-2 inset-shadow-2xs">
<div class="mx-auto container flex">
<a href="/" class="bg-gray-700 rounded text-white p-2 select-none">
<nav class="bg-orange-300 px-2 py-2 inset-shadow-2xs border-b-2 border-orange-900">
<div class="mx-auto container flex select-none">
<a href="/" class="outline-2 outline-gray-800 bg-gray-700 rounded text-white p-2">
<span class="text-green-200">>_</span>
Nixie CI ❄️
</a>
<div class="grow"></div>
<a href="/login" class="outline-2 outline-r-none hover:outline-emerald-900 outline-emerald-700 p-2 select-none rounded-l bg-emerald-600 text-white">Sign in</a>
<a href="/register" class="outline-2 hover:outline-emerald-900 outline-blue-700 p-2 select-none rounded-r bg-blue-600 text-white">Register</a>
</div>
</nav>
<div class="grow px-2">
<div class="mx-auto container">
<main class="grow px-2 flex flex-col inset-shadow-sm">
<div class="grow flex mx-auto container">
{% block content %}{% endblock content %}
</div>
</div>
<footer class="bg-orange-400 px-2 min-h-10 py-4 inset-shadow-2xs">
</main>
<footer class="bg-zinc-300 px-2 min-h-10 py-4">
<div class="mx-auto container font-bold">
{% block footer %}
&copy; Copyright 2026 by Hemera

View file

@ -0,0 +1,6 @@
{% macro text_input(label, name, id="",type="text") %}
<div class="flex flex-col">
<label for="{{id}}" class="font-bold">{{label}}:</label>
<input class="border rounded p-1 bg-gray-50" type="{{type}}" name="{{name}}" id="{{id}}" />
</div>
{% endmacro text_input %}

View file

@ -0,0 +1,28 @@
{% extends "base.tera.html" %}
{% import "inputs.tera.html" as inputs %}
{% block title %}
Login
{% endblock title %}
{% block content %}
<div class="grow sm:flex justify-center items-center">
<div class="border-2 border-zinc-300 sm:rounded-2xl lg:rounded-4xl my-2 px-20 pb-14 pt-10 sm:shadow-lg space-y-4">
<h1 class="font-bold text-3xl">Login</h1>
<form action="/login" method="POST" class="space-y-4">
{{ inputs::text_input(label="Username", name="username", id="username") }}
{{ inputs::text_input(label="Password", name="password", id="password", type="password") }}
<div class="flex flex-col">
<button type="submit" class="bg-blue-500 p-2 rounded-lg text-white">
Login
</button>
</div>
<div class="flex flex-col">
<a href="/register" class="bg-zinc-500 p-2 rounded-lg text-white text-center">
Register
</a>
</div>
</form>
</div>
</div>
{% endblock content %}