Add frontend prototype

This commit is contained in:
Simon Bruder 2024-07-03 14:19:58 +02:00
parent 83eaf1a5f5
commit ac70a4c119
Signed by: simon
GPG key ID: 347FF8699CDA0776
13 changed files with 360 additions and 0 deletions

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
static/vendor/* filter=lfs diff=lfs merge=lfs -text

105
Cargo.lock generated
View file

@ -308,6 +308,60 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "askama"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
dependencies = [
"askama_derive",
"askama_escape",
"humansize",
"num-traits",
"percent-encoding",
]
[[package]]
name = "askama_actix"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4b0dd17cfe203b00ba3853a89fba459ecf24c759b738b244133330607c78e55"
dependencies = [
"actix-web",
"askama",
]
[[package]]
name = "askama_derive"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
dependencies = [
"askama_parser",
"basic-toml",
"mime",
"mime_guess",
"proc-macro2",
"quote",
"serde",
"syn",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "askama_parser"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
dependencies = [
"nom",
]
[[package]]
name = "autocfg"
version = "1.3.0"
@ -335,6 +389,15 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "basic-toml"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.5.0"
@ -775,6 +838,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "humantime"
version = "2.1.0"
@ -840,6 +912,8 @@ version = "0.0.0"
dependencies = [
"actix-files",
"actix-web",
"askama",
"askama_actix",
"diesel",
"diesel_migrations",
"env_logger",
@ -854,6 +928,12 @@ version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "local-channel"
version = "0.1.5"
@ -930,6 +1010,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
@ -951,12 +1037,31 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.0"

View file

@ -12,6 +12,8 @@ license = "AGPL-3.0-or-later"
[dependencies]
actix-files = "0.6.6"
actix-web = "4.8.0"
askama = { version = "0.12.1", features = ["with-actix-web"] }
askama_actix = "0.14.0"
diesel = { version = "2.2.1", features = ["postgres", "r2d2", "uuid"] }
diesel_migrations = { version = "2.2.0", features = ["postgres"] }
env_logger = "0.11.3"

82
src/frontend/item.rs Normal file
View file

@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
use actix_web::{error, get, post, web, HttpRequest, Responder};
use askama_actix::Template;
use uuid::Uuid;
use crate::manage;
use crate::models::*;
use crate::DbPool;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(show_item)
.service(list_items)
.service(add_item)
.service(add_item_post);
}
#[derive(Template)]
#[template(path = "item_details.html")]
struct ItemDetails {
req: HttpRequest,
item: Item,
}
#[get("/item/{id}")]
async fn show_item(
req: HttpRequest,
pool: web::Data<DbPool>,
path: web::Path<Uuid>,
) -> actix_web::Result<impl Responder> {
let id = path.into_inner();
let item = web::block(move || manage::item::get(&mut pool.get().unwrap(), id))
.await?
.map_err(error::ErrorInternalServerError)?;
Ok(ItemDetails { req, item })
}
#[derive(Template)]
#[template(path = "item_list.html")]
struct ItemList {
req: HttpRequest,
items: Vec<Item>,
}
#[get("/items")]
async fn list_items(
req: HttpRequest,
pool: web::Data<DbPool>,
) -> actix_web::Result<impl Responder> {
let items = web::block(move || manage::item::get_all(&mut pool.get().unwrap()))
.await?
.map_err(error::ErrorInternalServerError)?;
Ok(ItemList { req, items })
}
#[derive(Template)]
#[template(path = "item_add.html")]
struct ItemAddForm {
req: HttpRequest,
data: Option<NewItem>,
}
#[get("/items/add")]
async fn add_item(req: HttpRequest) -> actix_web::Result<impl Responder> {
Ok(ItemAddForm { req, data: None })
}
#[post("/items/add")]
async fn add_item_post(
data: web::Form<NewItem>,
pool: web::Data<DbPool>,
) -> actix_web::Result<impl Responder> {
let item = web::block(move || manage::item::add(&mut pool.get().unwrap(), data.into_inner()))
.await?
.map_err(error::ErrorInternalServerError)?;
Ok(web::Redirect::to("/item/".to_owned() + &item.id.to_string()).see_other())
}

23
src/frontend/mod.rs Normal file
View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
mod item;
use actix_web::{get, web, HttpRequest, Responder};
use askama_actix::Template;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg.service(index).configure(item::config);
}
#[derive(Template)]
#[template(path = "base.html")]
struct Home {
req: HttpRequest,
}
#[get("/")]
async fn index(req: HttpRequest) -> impl Responder {
Home { req }
}

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pub mod api;
pub mod frontend;
pub mod manage;
pub mod models;
pub mod schema;

View file

@ -42,6 +42,7 @@ async fn main() -> std::io::Result<()> {
.app_data(web::Data::new(pool.clone()))
.service(web::scope("/api/v1").configure(li7y::api::v1::config))
.service(actix_files::Files::new("/static", &static_root))
.configure(li7y::frontend::config)
})
.bind((address, port))?
.run()

BIN
static/vendor/bootstrap.bundle.min.js (Stored with Git LFS) vendored Normal file

Binary file not shown.

BIN
static/vendor/bootstrap.min.css (Stored with Git LFS) vendored Normal file

Binary file not shown.

59
templates/base.html Normal file
View file

@ -0,0 +1,59 @@
{#
SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{% let branding = "li7y" -%}
<!DOCTYPE html>
<html class="h-100">
<head>
<meta charset="utf-8">
<title>{% block title %}{{ branding }}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/vendor/bootstrap.min.css">
</head>
<body class="d-flex flex-column h-100">
<nav class="navbar navbar-expand-lg bg-body-secondary">
<div class="container">
<a class="navbar-brand" href="/">{{ branding }}</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-expander">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar-expander">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link{% if req.path() == "/" %} active{% endif %}" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link{% if req.path() == "/items" %} active{% endif %}" href="/items">Items</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container my-4">
<div class="d-flex justify-content-between mb-3">
<div>
<h2>{% block page_title %}{% endblock %}</h2>
</div>
<div>
{% block page_actions %}{% endblock %}
</div>
</div>
{% block main %}{% endblock %}
</main>
<footer class="mt-auto py-3 bg-body-tertiary text-body-tertiary">
<div class="container">
<p class="mb-0">li7y is free software, released under the terms of the AGPL v3</p>
</div>
</footer>
<script src="/static/vendor/bootstrap.bundle.min.js"></script>
</body>
</html>

17
templates/item_add.html Normal file
View file

@ -0,0 +1,17 @@
{#
SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{% extends "base.html" %}
{% block title %}{% block page_title %}Add Item{% endblock %} {{ branding }}{% endblock %}
{% block main %}
<form method="POST">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" required{% if let Some(data) = data %} value="{{ data.name }}"{% endif %}>
</div>
<button type="submit" class="btn btn-primary">Add</button>
</form>
{% endblock %}

View file

@ -0,0 +1,30 @@
{#
SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{% extends "base.html" %}
{% block title %}{% block page_title %}{{ item.name }}{% endblock %} Item Details {{ branding }}{% endblock %}
{% block main %}
<table class="table">
<tbody>
<tr>
<th>
UUID
</th>
<td>
{{ item.id }}
</td>
</tr>
<tr>
<th>
Name
</th>
<td>
{{ item.name }}
</td>
</tr>
</tbody>
</table>
{% endblock %}

29
templates/item_list.html Normal file
View file

@ -0,0 +1,29 @@
{#
SPDX-FileCopyrightText: 2024 Simon Bruder <simon@sbruder.de>
SPDX-License-Identifier: AGPL-3.0-or-later
#}
{% extends "base.html" %}
{% block title %}{% block page_title %}Item List{% endblock %} {{ branding }}{% endblock %}
{% block page_actions %}
<a class="btn btn-primary" href="/items/add">Add</a>
{% endblock %}
{% block main %}
<table class="table">
<thead>
<tr>
<th>UUID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for item in items -%}
<tr>
<td><a href="/item/{{ item.id }}">{{ item.id }}</a></td>
<td>{{ item.name }}</td>
</tr>
{% endfor -%}
</tbody>
</table>
{% endblock %}