Add frontend prototype
This commit is contained in:
parent
83eaf1a5f5
commit
ac70a4c119
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal 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
105
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
82
src/frontend/item.rs
Normal 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
23
src/frontend/mod.rs
Normal 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 }
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
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
BIN
static/vendor/bootstrap.min.css
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
59
templates/base.html
Normal file
59
templates/base.html
Normal 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
17
templates/item_add.html
Normal 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 %}
|
30
templates/item_details.html
Normal file
30
templates/item_details.html
Normal 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
29
templates/item_list.html
Normal 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 %}
|
Loading…
Reference in a new issue