Style login page
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
a1474dab43
commit
79305724f2
6 changed files with 85 additions and 49 deletions
|
|
@ -2,7 +2,9 @@ use std::sync::LazyLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
use axum::http::StatusCode;
|
||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum_login::AuthManagerLayerBuilder;
|
use axum_login::AuthManagerLayerBuilder;
|
||||||
use axum_login::AuthnBackend;
|
use axum_login::AuthnBackend;
|
||||||
|
|
@ -16,7 +18,6 @@ use sqlx::SqlitePool;
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tokio::task::AbortHandle;
|
use tokio::task::AbortHandle;
|
||||||
use tower_livereload::LiveReloadLayer;
|
use tower_livereload::LiveReloadLayer;
|
||||||
|
|
@ -28,6 +29,29 @@ use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
pub mod users;
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
run().await
|
run().await
|
||||||
|
|
@ -93,7 +117,7 @@ pub struct AppState {
|
||||||
pub db: SqlitePool,
|
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());
|
LazyLock::new(|| Tera::new("templates/**.tera.html").unwrap().into());
|
||||||
|
|
||||||
async fn run() -> anyhow::Result<()> {
|
async fn run() -> anyhow::Result<()> {
|
||||||
|
|
@ -140,7 +164,7 @@ async fn run() -> anyhow::Result<()> {
|
||||||
.iter()
|
.iter()
|
||||||
.any(|ev| !matches!(ev.event.kind, EventKind::Access(_)))
|
.any(|ev| !matches!(ev.event.kind, EventKind::Access(_)))
|
||||||
{
|
{
|
||||||
match TERA.blocking_write().full_reload() {
|
match TERA.write().unwrap().full_reload() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
reloader.reload();
|
reloader.reload();
|
||||||
}
|
}
|
||||||
|
|
@ -192,14 +216,10 @@ async fn shutdown_signal(handle: AbortHandle) {
|
||||||
handle.abort();
|
handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn render_template(name: &str, context: &Context) -> tera::Result<String> {
|
pub fn render_template(name: &str, context: &Context) -> tera::Result<String> {
|
||||||
TERA.read().await.render(name, context)
|
TERA.read().unwrap().render(name, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn show_protected() -> Html<String> {
|
async fn show_protected() -> Html<String> {
|
||||||
Html(
|
Html(render_template("protected.tera.html", &Context::default()).unwrap())
|
||||||
render_template("protected.tera.html", &Context::default())
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,15 @@ use axum::http::StatusCode;
|
||||||
use axum::response::Html;
|
use axum::response::Html;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Redirect;
|
use axum::response::Redirect;
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
use crate::AuthSession;
|
use crate::AuthSession;
|
||||||
use crate::UserCredentials;
|
use crate::UserCredentials;
|
||||||
|
use crate::WebResult;
|
||||||
|
use crate::render_template;
|
||||||
|
|
||||||
pub async fn show_login() -> Html<String> {
|
pub async fn show_login() -> WebResult<Html<String>> {
|
||||||
format!(
|
Ok(render_template("users/login.tera.html", &Context::default()).map(Html)?)
|
||||||
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 do_login(
|
pub async fn do_login(
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,15 @@ use axum::response::Html;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::response::Redirect;
|
use axum::response::Redirect;
|
||||||
use password_auth::generate_hash;
|
use password_auth::generate_hash;
|
||||||
|
use tera::Context;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::UserCredentials;
|
use crate::UserCredentials;
|
||||||
|
use crate::WebResult;
|
||||||
|
use crate::render_template;
|
||||||
|
|
||||||
pub async fn show_register() -> Html<String> {
|
pub async fn show_register() -> WebResult<Html<String>> {
|
||||||
format!(
|
Ok(render_template("users/register.tera.html", &Context::default()).map(Html)?)
|
||||||
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 do_register(
|
pub async fn do_register(
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,24 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="min-h-screen flex flex-col">
|
<body class="min-h-screen flex flex-col">
|
||||||
<nav class="bg-orange-300 px-2 py-2 inset-shadow-2xs">
|
<nav class="bg-orange-300 px-2 py-2 inset-shadow-2xs border-b-2 border-orange-900">
|
||||||
<div class="mx-auto container flex">
|
<div class="mx-auto container flex select-none">
|
||||||
<a href="/" class="bg-gray-700 rounded text-white p-2 select-none">
|
<a href="/" class="outline-2 outline-gray-800 bg-gray-700 rounded text-white p-2">
|
||||||
<span class="text-green-200">>_</span>
|
<span class="text-green-200">>_</span>
|
||||||
Nixie CI ❄️
|
Nixie CI ❄️
|
||||||
</a>
|
</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>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="grow px-2">
|
<main class="grow px-2 flex flex-col inset-shadow-sm">
|
||||||
<div class="mx-auto container">
|
<div class="grow flex mx-auto container">
|
||||||
{% block content %}{% endblock content %}
|
{% block content %}{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
<footer class="bg-orange-400 px-2 min-h-10 py-4 inset-shadow-2xs">
|
<footer class="bg-zinc-300 px-2 min-h-10 py-4">
|
||||||
<div class="mx-auto container font-bold">
|
<div class="mx-auto container font-bold">
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
© Copyright 2026 by Hemera
|
© Copyright 2026 by Hemera
|
||||||
|
|
|
||||||
6
nixie-server/templates/inputs.tera.html
Normal file
6
nixie-server/templates/inputs.tera.html
Normal 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 %}
|
||||||
28
nixie-server/templates/users/login.tera.html
Normal file
28
nixie-server/templates/users/login.tera.html
Normal 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 %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue