120 lines
3.1 KiB
Rust
120 lines
3.1 KiB
Rust
//! The page shell: navbar, layouts, and the status blocks every page reuses.
|
|
//!
|
|
//! `templates/` mirrors `src/` directory for directory, so this module's
|
|
//! markup is in `templates/ui/` and a page's markup is in
|
|
//! `templates/<the page's path under src>/`.
|
|
|
|
use askama::Template;
|
|
use axum::http::HeaderMap;
|
|
|
|
pub(crate) const SESSION_COOKIE: &str = "analytics_token";
|
|
|
|
/// Navbar state. Every full-page template struct carries one of these, because
|
|
/// `ui/base.html` renders `ui/navbar.html` unconditionally.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct Nav {
|
|
pub authenticated: bool,
|
|
pub role: String,
|
|
pub active: &'static str,
|
|
}
|
|
|
|
impl Nav {
|
|
/// `active` is the nav link to highlight: `"admin"`, `"analytics"`,
|
|
/// `"login"`, or `""` for pages that are not themselves nav entries.
|
|
pub(crate) fn new(headers: &HeaderMap, active: &'static str) -> Self {
|
|
Self {
|
|
authenticated: crate::cookie_value(headers, SESSION_COOKIE).is_some(),
|
|
role: String::new(),
|
|
active,
|
|
}
|
|
}
|
|
|
|
/// The admin page is the only one that learns the caller's role, so it is
|
|
/// the only one that can show the role badge.
|
|
pub(crate) fn with_role(mut self, role: String) -> Self {
|
|
self.role = role;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Default for Nav {
|
|
fn default() -> Self {
|
|
Self {
|
|
authenticated: false,
|
|
role: String::new(),
|
|
active: "",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The swap target every form POST answers with.
|
|
#[derive(Template)]
|
|
#[template(path = "ui/alert_fragment.html")]
|
|
pub(crate) struct Alert<'a> {
|
|
success: bool,
|
|
title: &'a str,
|
|
message: &'a str,
|
|
}
|
|
|
|
impl<'a> Alert<'a> {
|
|
pub(crate) fn error(title: &'a str, message: &'a str) -> Self {
|
|
Self {
|
|
success: false,
|
|
title,
|
|
message,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn success(title: &'a str, message: &'a str) -> Self {
|
|
Self {
|
|
success: true,
|
|
title,
|
|
message,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A one-line notice, for places where the alert card is too heavy.
|
|
#[derive(Template)]
|
|
#[template(path = "ui/notice.html")]
|
|
pub(crate) struct Notice<'a> {
|
|
error: bool,
|
|
message: &'a str,
|
|
login_link: bool,
|
|
}
|
|
|
|
impl<'a> Notice<'a> {
|
|
pub(crate) fn error(message: &'a str) -> Self {
|
|
Self {
|
|
error: true,
|
|
message,
|
|
login_link: false,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn login_required(message: &'a str) -> Self {
|
|
Self {
|
|
error: true,
|
|
message,
|
|
login_link: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Standalone page for load failures that are not worth a redirect.
|
|
#[derive(Template)]
|
|
#[template(path = "ui/error.html")]
|
|
pub(crate) struct ErrorPage<'a> {
|
|
pub nav: Nav,
|
|
pub heading: &'a str,
|
|
pub message: &'a str,
|
|
}
|
|
|
|
/// Renders a template, or a plain error paragraph if the template itself
|
|
/// fails. Askama only fails on `fmt` errors, so this is a formality.
|
|
pub(crate) fn render<T: Template>(template: &T) -> String {
|
|
template
|
|
.render()
|
|
.unwrap_or_else(|error| format!("<p class=\"error\">Template error: {error}</p>"))
|
|
}
|