This commit is contained in:
2023-08-29 21:34:01 +02:00
parent 3719dfcef6
commit b80cae1278
11 changed files with 431 additions and 237 deletions

12
rust/Cargo.lock generated
View File

@@ -1225,6 +1225,7 @@ dependencies = [
"opentelemetry", "opentelemetry",
"opentelemetry-semantic-conventions", "opentelemetry-semantic-conventions",
"thrift", "thrift",
"tokio",
] ]
[[package]] [[package]]
@@ -1270,6 +1271,8 @@ dependencies = [
"rand", "rand",
"regex", "regex",
"thiserror", "thiserror",
"tokio",
"tokio-stream",
] ]
[[package]] [[package]]
@@ -1318,6 +1321,7 @@ dependencies = [
"tower", "tower",
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-log",
"tracing-opentelemetry", "tracing-opentelemetry",
"tracing-subscriber", "tracing-subscriber",
"uuid", "uuid",
@@ -1667,9 +1671,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.101.3" version = "0.101.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@@ -1808,9 +1812,9 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.8" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]

View File

@@ -11,10 +11,18 @@ path = "src/main.rs"
opt-level = 0 opt-level = 0
lto = "off" lto = "off"
[dependencies] [dependencies.opentelemetry]
opentelemetry = "0.20" version = "0.20"
tracing-opentelemetry = "0.20"
opentelemetry-jaeger = "0.19" [dependencies.tracing-opentelemetry]
version = "0.20"
[dependencies.tracing-log]
version = "0.1"
[dependencies.opentelemetry-jaeger]
version = "0.19"
features = ["rt-tokio"]
[dependencies.http] [dependencies.http]
version = "0.2" version = "0.2"

View File

@@ -1,4 +1,5 @@
use axum::{extract::State, middleware::Next, response::IntoResponse}; use axum::{extract::State, middleware::Next, response::IntoResponse};
use tracing::Instrument;
use hyper::Request; use hyper::Request;
@@ -11,28 +12,33 @@ pub enum Config {
Disabled { assume_user: String }, Disabled { assume_user: String },
} }
#[tracing::instrument(skip(state, request, next))] #[tracing::instrument(name = "check_auth", skip(state, request, next))]
pub async fn authorize<B>( pub async fn authorize<B>(
State(state): State<AppState>, State(state): State<AppState>,
mut request: Request<B>, mut request: Request<B>,
next: Next<B>, next: Next<B>,
) -> Result<impl IntoResponse, Error> { ) -> Result<impl IntoResponse, Error> {
let current_user = match state.auth_config { let current_user = async {
let user = match state.auth_config {
Config::Disabled { assume_user } => { Config::Disabled { assume_user } => {
match models::user::User::find_by_name(&state.database_pool, &assume_user).await? { let user =
match models::user::User::find_by_name(&state.database_pool, &assume_user)
.await?
{
Some(user) => user, Some(user) => user,
None => { None => {
return Err(Error::Request(RequestError::AuthenticationUserNotFound { return Err(Error::Request(RequestError::AuthenticationUserNotFound {
username: assume_user, username: assume_user,
})) }))
} }
} };
tracing::info!(?user, "auth disabled, requested user exists");
user
} }
Config::Enabled => { Config::Enabled => {
let Some(username) = request.headers().get("x-auth-username") else { let Some(username) = request.headers().get("x-auth-username") else {
return Err(Error::Request(RequestError::AuthenticationHeaderMissing)); return Err(Error::Request(RequestError::AuthenticationHeaderMissing));
}; };
let username = username let username = username
.to_str() .to_str()
.map_err(|error| { .map_err(|error| {
@@ -42,16 +48,25 @@ pub async fn authorize<B>(
})? })?
.to_string(); .to_string();
match models::user::User::find_by_name(&state.database_pool, &username).await? { let user = match models::user::User::find_by_name(&state.database_pool, &username)
.await?
{
Some(user) => user, Some(user) => user,
None => { None => {
tracing::warn!(username, "auth rejected, user not found");
return Err(Error::Request(RequestError::AuthenticationUserNotFound { return Err(Error::Request(RequestError::AuthenticationUserNotFound {
username, username,
})) }));
}
}
} }
}; };
tracing::info!(?user, "auth successful");
user
}
};
Ok(user)
}
.instrument(tracing::debug_span!("authorize"))
.await?;
request.extensions_mut().insert(current_user); request.extensions_mut().insert(current_user);
Ok(next.run(request).await) Ok(next.run(request).await)

69
rust/src/cmd.rs Normal file
View File

@@ -0,0 +1,69 @@
use clap::{Parser, Subcommand, ValueEnum};
#[derive(ValueEnum, Clone, Copy, Debug)]
pub enum BoolArg {
True,
False,
}
impl From<BoolArg> for bool {
fn from(arg: BoolArg) -> bool {
match arg {
BoolArg::True => true,
BoolArg::False => false,
}
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[arg(long)]
pub database_url: String,
#[arg(long, value_enum, default_value_t = BoolArg::False)]
pub enable_opentelemetry: BoolArg,
#[arg(long, value_enum, default_value_t = BoolArg::False)]
pub enable_tokio_console: BoolArg,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
Serve(Serve),
#[command(subcommand)]
Admin(Admin),
Migrate,
}
#[derive(Parser, Debug)]
pub struct Serve {
#[arg(long, default_value_t = 3000)]
pub port: u16,
#[arg(long)]
pub bind: String,
#[arg(long, name = "USERNAME")]
pub disable_auth_and_assume_user: Option<String>,
}
#[derive(Subcommand, Debug)]
pub enum Admin {
#[command(subcommand)]
User(UserCommand),
}
#[derive(Subcommand, Debug)]
pub enum UserCommand {
Create(UserCreate),
}
#[derive(Parser, Debug)]
pub struct UserCreate {
#[arg(long)]
pub username: String,
#[arg(long)]
pub fullname: String,
}

View File

@@ -3,6 +3,7 @@ use uuid::Uuid;
use std::fmt; use std::fmt;
pub mod auth; pub mod auth;
pub mod cmd;
pub mod error; pub mod error;
pub mod htmx; pub mod htmx;
pub mod models; pub mod models;

View File

@@ -1,57 +1,11 @@
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::pin::Pin;
use std::process::ExitCode; use std::process::ExitCode;
use std::str::FromStr; use std::str::FromStr;
use clap::{Parser, Subcommand}; use packager::{auth, cmd, models, routing, sqlite, telemetry, AppState, ClientState, Error};
use packager::{auth, models, routing, sqlite, telemetry, AppState, ClientState, Error}; use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(long)]
database_url: String,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
Serve(Serve),
#[command(subcommand)]
Admin(Admin),
Migrate,
}
#[derive(Parser, Debug)]
struct Serve {
#[arg(long, default_value_t = 3000)]
port: u16,
#[arg(long)]
bind: String,
#[arg(long, name = "USERNAME")]
disable_auth_and_assume_user: Option<String>,
}
#[derive(Subcommand, Debug)]
enum Admin {
#[command(subcommand)]
User(UserCommand),
}
#[derive(Subcommand, Debug)]
enum UserCommand {
Create(UserCreate),
}
#[derive(Parser, Debug)]
struct UserCreate {
#[arg(long)]
username: String,
#[arg(long)]
fullname: String,
}
struct MainResult(Result<(), Error>); struct MainResult(Result<(), Error>);
@@ -75,15 +29,29 @@ impl From<Error> for MainResult {
#[tokio::main] #[tokio::main]
async fn main() -> MainResult { async fn main() -> MainResult {
telemetry::init_tracing(); let args = cmd::Args::parse();
let args = Args::parse(); telemetry::init_tracing(
if args.enable_opentelemetry.into() {
telemetry::OpenTelemetryConfig::Enabled
} else {
telemetry::OpenTelemetryConfig::Disabled
},
if args.enable_tokio_console.into() {
telemetry::TokioConsoleConfig::Enabled
} else {
telemetry::TokioConsoleConfig::Disabled
},
args,
|args| -> Pin<Box<dyn std::future::Future<Output = MainResult>>> {
Box::pin(async move {
match args.command { match args.command {
Command::Serve(serve_args) => { cmd::Command::Serve(serve_args) => {
if let Err(e) = sqlite::migrate(&args.database_url).await { if let Err(e) = sqlite::migrate(&args.database_url).await {
return <_ as Into<Error>>::into(e).into(); return <_ as Into<Error>>::into(e).into();
} }
let database_pool = match sqlite::init_database_pool(&args.database_url).await { let database_pool =
match sqlite::init_database_pool(&args.database_url).await {
Ok(pool) => pool, Ok(pool) => pool,
Err(e) => return <_ as Into<Error>>::into(e).into(), Err(e) => return <_ as Into<Error>>::into(e).into(),
}; };
@@ -91,7 +59,9 @@ async fn main() -> MainResult {
let state = AppState { let state = AppState {
database_pool, database_pool,
client_state: ClientState::new(), client_state: ClientState::new(),
auth_config: if let Some(assume_user) = serve_args.disable_auth_and_assume_user { auth_config: if let Some(assume_user) =
serve_args.disable_auth_and_assume_user
{
auth::Config::Disabled { assume_user } auth::Config::Disabled { assume_user }
} else { } else {
auth::Config::Enabled auth::Config::Enabled
@@ -105,7 +75,10 @@ async fn main() -> MainResult {
let addr = SocketAddr::from(( let addr = SocketAddr::from((
IpAddr::from_str(&serve_args.bind) IpAddr::from_str(&serve_args.bind)
.map_err(|error| { .map_err(|error| {
format!("error parsing bind address {}: {}", &serve_args.bind, error) format!(
"error parsing bind address {}: {}",
&serve_args.bind, error
)
}) })
.unwrap(), .unwrap(),
serve_args.port, serve_args.port,
@@ -120,10 +93,11 @@ async fn main() -> MainResult {
return <hyper::Error as Into<Error>>::into(e).into(); return <hyper::Error as Into<Error>>::into(e).into();
} }
} }
Command::Admin(admin_command) => match admin_command { cmd::Command::Admin(admin_command) => match admin_command {
Admin::User(cmd) => match cmd { cmd::Admin::User(cmd) => match cmd {
UserCommand::Create(user) => { cmd::UserCommand::Create(user) => {
let database_pool = match sqlite::init_database_pool(&args.database_url).await { let database_pool =
match sqlite::init_database_pool(&args.database_url).await {
Ok(pool) => pool, Ok(pool) => pool,
Err(e) => return <_ as Into<Error>>::into(e).into(), Err(e) => return <_ as Into<Error>>::into(e).into(),
}; };
@@ -137,11 +111,11 @@ async fn main() -> MainResult {
) )
.await .await
.map_err(|error| match error { .map_err(|error| match error {
models::Error::Query(models::QueryError::Duplicate { description: _ }) => { models::Error::Query(models::QueryError::Duplicate {
Error::Command(packager::CommandError::UserExists { description: _,
}) => Error::Command(packager::CommandError::UserExists {
username: user.username.clone(), username: user.username.clone(),
}) }),
}
_ => Error::Model(error), _ => Error::Model(error),
}) { }) {
Ok(id) => id, Ok(id) => id,
@@ -157,7 +131,7 @@ async fn main() -> MainResult {
} }
}, },
}, },
Command::Migrate => { cmd::Command::Migrate => {
if let Err(e) = sqlite::migrate(&args.database_url).await { if let Err(e) = sqlite::migrate(&args.database_url).await {
return <_ as Into<Error>>::into(e).into(); return <_ as Into<Error>>::into(e).into();
} }
@@ -165,6 +139,9 @@ async fn main() -> MainResult {
println!("Migrations successfully applied"); println!("Migrations successfully applied");
} }
} }
MainResult(Ok(())) MainResult(Ok(()))
})
},
)
.await
} }

View File

@@ -4,6 +4,8 @@ use crate::Context;
use futures::{TryFutureExt, TryStreamExt}; use futures::{TryFutureExt, TryStreamExt};
use uuid::Uuid; use uuid::Uuid;
use tracing::Instrument;
pub struct Inventory { pub struct Inventory {
pub categories: Vec<Category>, pub categories: Vec<Category>,
} }
@@ -12,6 +14,7 @@ impl Inventory {
#[tracing::instrument] #[tracing::instrument]
pub async fn load(ctx: &Context, pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<Self, Error> { pub async fn load(ctx: &Context, pool: &sqlx::Pool<sqlx::Sqlite>) -> Result<Self, Error> {
let user_id = ctx.user.id.to_string(); let user_id = ctx.user.id.to_string();
let categories = async {
let mut categories = sqlx::query_as!( let mut categories = sqlx::query_as!(
DbCategoryRow, DbCategoryRow,
"SELECT "SELECT
@@ -33,6 +36,11 @@ impl Inventory {
category.populate_items(ctx, pool).await?; category.populate_items(ctx, pool).await?;
} }
Ok::<_, Error>(categories)
}
.instrument(tracing::info_span!("packager::query", "query"))
.await?;
Ok(Self { categories }) Ok(Self { categories })
} }
} }

View File

@@ -1,10 +1,15 @@
use axum::{ use axum::{
error_handling::HandleErrorLayer,
http::header::HeaderMap, http::header::HeaderMap,
http::StatusCode,
middleware, middleware,
routing::{get, post}, routing::{get, post},
Router, BoxError, Router,
}; };
use std::time::Duration;
use tower::{timeout::TimeoutLayer, ServiceBuilder};
use crate::{AppState, Error, RequestError, TopLevelPage}; use crate::{AppState, Error, RequestError, TopLevelPage};
use super::auth; use super::auth;
@@ -39,6 +44,13 @@ pub fn router(state: AppState) -> Router {
}) })
}), }),
) )
.route(
"/slow",
get(|| async {
tokio::time::sleep(Duration::from_secs(1)).await;
"Ok"
}),
)
.route("/debug", get(debug)) .route("/debug", get(debug))
.merge( .merge(
// thse are routes that require authentication // thse are routes that require authentication
@@ -119,6 +131,15 @@ pub fn router(state: AppState) -> Router {
auth::authorize, auth::authorize,
)), )),
) )
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(|_: BoxError| async {
tracing::warn!("request timeout");
StatusCode::REQUEST_TIMEOUT
}))
.layer(TimeoutLayer::new(Duration::from_millis(500))),
)
// .propagate_x_request_id()
.fallback(|| async { .fallback(|| async {
Error::Request(RequestError::NotFound { Error::Request(RequestError::NotFound {
message: "no route found".to_string(), message: "no route found".to_string(),

View File

@@ -1,5 +1,7 @@
use std::time; use std::time;
use tracing::Instrument;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use sqlx::ConnectOptions; use sqlx::ConnectOptions;
pub use sqlx::{Pool, Sqlite}; pub use sqlx::{Pool, Sqlite};
@@ -28,10 +30,13 @@ pub async fn migrate(url: &str) -> Result<(), StartError> {
.connect_with( .connect_with(
SqliteConnectOptions::from_str(url)? SqliteConnectOptions::from_str(url)?
.pragma("foreign_keys", "0") .pragma("foreign_keys", "0")
.log_statements(log::LevelFilter::Debug), .log_statements(log::LevelFilter::Warn),
) )
.await?; .await?;
sqlx::migrate!().run(&pool).await?; async { sqlx::migrate!().run(&pool).await }
.instrument(tracing::info_span!("packager::query", "migration"))
.await?;
Ok(()) Ok(())
} }

View File

@@ -1,12 +1,15 @@
use std::fmt; use std::fmt;
use std::future::Future;
use std::io; use std::io;
use std::pin::Pin;
use std::time::Duration; use std::time::Duration;
use axum::Router; use axum::Router;
use http::Request; use http::Request;
use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer}; use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer};
use tracing::{Level, Span}; use tracing::Span;
use tracing_log::LogTracer;
use tracing_subscriber::{ use tracing_subscriber::{
filter::{LevelFilter, Targets}, filter::{LevelFilter, Targets},
fmt::{format::Format, Layer}, fmt::{format::Format, Layer},
@@ -16,13 +19,31 @@ use tracing_subscriber::{
}; };
use uuid::Uuid; use uuid::Uuid;
use opentelemetry::global; use opentelemetry::{global, runtime::Tokio};
pub fn otel_init(f: impl FnOnce() -> ()) { pub enum OpenTelemetryConfig {
f() Enabled,
Disabled,
} }
pub fn init_tracing() { pub enum TokioConsoleConfig {
Enabled,
Disabled,
}
pub async fn init_tracing<Func, T>(
opentelemetry_config: OpenTelemetryConfig,
tokio_console_config: TokioConsoleConfig,
args: crate::cmd::Args,
f: Func,
) -> T
where
Func: FnOnce(crate::cmd::Args) -> Pin<Box<dyn Future<Output = T>>>,
T: std::process::Termination,
{
let mut shutdown_functions: Vec<Box<dyn FnOnce() -> Result<(), Box<dyn std::error::Error>>>> =
vec![];
// default is the Full format, there is no way to specify this, but it can be // default is the Full format, there is no way to specify this, but it can be
// overridden via builder methods // overridden via builder methods
let stdout_format = Format::default() let stdout_format = Format::default()
@@ -37,53 +58,81 @@ pub fn init_tracing() {
.with_writer(io::stdout); .with_writer(io::stdout);
let stdout_filter = Targets::new() let stdout_filter = Targets::new()
.with_default(LevelFilter::OFF) .with_default(LevelFilter::WARN)
.with_targets(vec![ .with_targets(vec![
(env!("CARGO_PKG_NAME"), Level::DEBUG), (env!("CARGO_PKG_NAME"), LevelFilter::DEBUG),
// this is for axum requests ("request", LevelFilter::DEBUG),
("request", Level::DEBUG), ("runtime", LevelFilter::OFF),
// required for tokio-console as by the docs ("sqlx", LevelFilter::TRACE),
// ("tokio", Level::TRACE),
// ("runtime", Level::TRACE),
]); ]);
let stdout_layer = stdout_layer.with_filter(stdout_filter); let stdout_layer = stdout_layer.with_filter(stdout_filter);
let console_layer = console_subscriber::Builder::default().spawn(); let console_layer = match tokio_console_config {
TokioConsoleConfig::Enabled => Some(console_subscriber::Builder::default().spawn()),
TokioConsoleConfig::Disabled => None,
};
let opentelemetry_layer = match opentelemetry_config {
OpenTelemetryConfig::Enabled => {
global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new()); global::set_text_map_propagator(opentelemetry_jaeger::Propagator::new());
// Sets up the machinery needed to export data to Jaeger // Sets up the machinery needed to export data to Jaeger
// There are other OTel crates that provide pipelines for the vendors // There are other OTel crates that provide pipelines for the vendors
// mentioned earlier. // mentioned earlier.
let tracer = opentelemetry_jaeger::new_agent_pipeline() let tracer = opentelemetry_jaeger::new_agent_pipeline()
.with_service_name(env!("CARGO_PKG_NAME")) .with_service_name(env!("CARGO_PKG_NAME"))
.install_simple() .with_max_packet_size(20_000)
.with_auto_split_batch(true)
.install_batch(Tokio)
.unwrap(); .unwrap();
let opentelemetry_filter = Targets::new() let opentelemetry_filter = {
.with_default(LevelFilter::OFF) Targets::new()
.with_default(LevelFilter::DEBUG)
.with_targets(vec![ .with_targets(vec![
(env!("CARGO_PKG_NAME"), Level::DEBUG), (env!("CARGO_PKG_NAME"), LevelFilter::DEBUG),
// this is for axum requests ("request", LevelFilter::DEBUG),
("request", Level::DEBUG), ("runtime", LevelFilter::OFF),
// required for tokio-console as by the docs ("sqlx", LevelFilter::DEBUG),
// ("tokio", Level::TRACE), ])
// ("runtime", Level::TRACE), };
]);
let opentelemetry = tracing_opentelemetry::layer() let opentelemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer);
.with_tracer(tracer) // .with_filter(opentelemetry_filter);
.with_filter(opentelemetry_filter);
shutdown_functions.push(Box::new(|| {
println!("shutting down otel");
global::shutdown_tracer_provider();
Ok(())
}));
println!("set up otel");
Some(opentelemetry_layer)
}
OpenTelemetryConfig::Disabled => None,
};
let registry = Registry::default() let registry = Registry::default()
.with(console_layer) .with(console_layer)
.with(opentelemetry) .with(opentelemetry_layer)
// just an example, you can actuall pass Options here for layers that might be // just an example, you can actuall pass Options here for layers that might be
// set/unset at runtime // set/unset at runtime
.with(Some(stdout_layer)) .with(Some(stdout_layer))
.with(None::<Layer<_>>); .with(None::<Layer<_>>);
tracing::subscriber::set_global_default(registry).unwrap(); tracing::subscriber::set_global_default(registry).unwrap();
tracing::debug!("tracing setup finished");
tracing_log::log_tracer::Builder::new().init().unwrap();
let result = f(args).await;
for shutdown_func in shutdown_functions {
shutdown_func().unwrap();
}
result
} }
struct Latency(Duration); struct Latency(Duration);

View File

@@ -7,12 +7,17 @@ use uuid::Uuid;
pub struct Inventory; pub struct Inventory;
impl Inventory { impl Inventory {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory",
fields(component = "Inventory")
)]
pub fn build( pub fn build(
active_category: Option<&models::inventory::Category>, active_category: Option<&models::inventory::Category>,
categories: &Vec<models::inventory::Category>, categories: &Vec<models::inventory::Category>,
edit_item_id: Option<Uuid>, edit_item_id: Option<Uuid>,
) -> Markup { ) -> Markup {
tracing::info!("building inventory HTML component");
html!( html!(
div id="pkglist-item-manager" { div id="pkglist-item-manager" {
div ."p-8" ."grid" ."grid-cols-4" ."gap-5" { div ."p-8" ."grid" ."grid-cols-4" ."gap-5" {
@@ -37,7 +42,11 @@ impl Inventory {
pub struct InventoryCategoryList; pub struct InventoryCategoryList;
impl InventoryCategoryList { impl InventoryCategoryList {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_category_list",
fields(component = "InventoryCategoryList")
)]
pub fn build( pub fn build(
active_category: Option<&models::inventory::Category>, active_category: Option<&models::inventory::Category>,
categories: &Vec<models::inventory::Category>, categories: &Vec<models::inventory::Category>,
@@ -144,7 +153,11 @@ impl InventoryCategoryList {
pub struct InventoryItemList; pub struct InventoryItemList;
impl InventoryItemList { impl InventoryItemList {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_item_list",
fields(component = "InventoryItemList")
)]
pub fn build(edit_item_id: Option<Uuid>, items: &Vec<models::inventory::Item>) -> Markup { pub fn build(edit_item_id: Option<Uuid>, items: &Vec<models::inventory::Item>) -> Markup {
let biggest_item_weight: i64 = items.iter().map(|item| item.weight).max().unwrap_or(1); let biggest_item_weight: i64 = items.iter().map(|item| item.weight).max().unwrap_or(1);
html!( html!(
@@ -320,7 +333,11 @@ impl InventoryItemList {
pub struct InventoryNewItemFormName; pub struct InventoryNewItemFormName;
impl InventoryNewItemFormName { impl InventoryNewItemFormName {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_new_item_form_name",
fields(component = "InventoryNewItemFormName")
)]
pub fn build(value: Option<&str>, error: bool) -> Markup { pub fn build(value: Option<&str>, error: bool) -> Markup {
html!( html!(
div div
@@ -368,7 +385,11 @@ impl InventoryNewItemFormName {
pub struct InventoryNewItemFormWeight; pub struct InventoryNewItemFormWeight;
impl InventoryNewItemFormWeight { impl InventoryNewItemFormWeight {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_new_item_form_weight",
fields(component = "InventoryNewItemFormWeight")
)]
pub fn build() -> Markup { pub fn build() -> Markup {
html!( html!(
div div
@@ -411,7 +432,11 @@ impl InventoryNewItemFormWeight {
pub struct InventoryNewItemFormCategory; pub struct InventoryNewItemFormCategory;
impl InventoryNewItemFormCategory { impl InventoryNewItemFormCategory {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_new_item_form_category",
fields(component = "InventoryNewItemFormCategory")
)]
pub fn build( pub fn build(
active_category: Option<&models::inventory::Category>, active_category: Option<&models::inventory::Category>,
categories: &Vec<models::inventory::Category>, categories: &Vec<models::inventory::Category>,
@@ -452,7 +477,11 @@ impl InventoryNewItemFormCategory {
pub struct InventoryNewItemForm; pub struct InventoryNewItemForm;
impl InventoryNewItemForm { impl InventoryNewItemForm {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_new_item_form",
fields(component = "InventoryNewItemForm")
)]
pub fn build( pub fn build(
active_category: Option<&models::inventory::Category>, active_category: Option<&models::inventory::Category>,
categories: &Vec<models::inventory::Category>, categories: &Vec<models::inventory::Category>,
@@ -499,7 +528,11 @@ impl InventoryNewItemForm {
pub struct InventoryNewCategoryForm; pub struct InventoryNewCategoryForm;
impl InventoryNewCategoryForm { impl InventoryNewCategoryForm {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_new_category_form",
fields(component = "InventoryNewCategoryForm")
)]
pub fn build() -> Markup { pub fn build() -> Markup {
html!( html!(
form form
@@ -554,7 +587,11 @@ impl InventoryNewCategoryForm {
pub struct InventoryItem; pub struct InventoryItem;
impl InventoryItem { impl InventoryItem {
#[tracing::instrument] #[tracing::instrument(
target = "packager::html::build",
name = "build_inventory_item",
fields(component = "InventoryItem")
)]
pub fn build(_state: &ClientState, item: &models::inventory::InventoryItem) -> Markup { pub fn build(_state: &ClientState, item: &models::inventory::InventoryItem) -> Markup {
html!( html!(
div ."p-8" { div ."p-8" {