This commit is contained in:
2026-07-16 18:01:58 +02:00
parent b62bba0173
commit ae0176b49a
13 changed files with 371 additions and 8 deletions
-1
View File
@@ -138,7 +138,6 @@ fn sidebar_shell(props: &SidebarShellProps) -> Html {
}
}
/// Props for the AdminCheckWrapper component.
#[derive(Properties, PartialEq)]
pub struct AdminCheckWrapperProps {
+1
View File
@@ -6,6 +6,7 @@
//! By re-exporting them here, other parts of the application can import page
//! components more conveniently using `crate::pages::*`.
pub mod basic_pages;
pub mod qr;
pub mod setup;
pub mod sidebar;
pub mod ticket;
+118
View File
@@ -0,0 +1,118 @@
use base64::{Engine, engine::general_purpose::STANDARD};
use gloo_net::http::Request;
use serde::{Deserialize, Serialize};
use yew::prelude::*;
#[derive(Deserialize, Clone)]
struct QrResponse {
raw_token: String,
}
#[derive(Properties, PartialEq)]
pub struct QrManagerProps {
pub user_id: i16,
}
#[derive(Serialize)]
pub struct QrPayload {
user_id: i16,
}
#[function_component(QrManager)]
pub fn qr_manager(props: &QrManagerProps) -> Html {
let qr_image_data = use_state(|| None::<String>);
let error_message = use_state(|| None::<String>);
let user_id = props.user_id;
let on_generate_click = {
let qr_image_data = qr_image_data.clone();
let error_message = error_message.clone();
Callback::from(move |_| {
let qr_image_data = qr_image_data.clone();
let error_message = error_message.clone();
wasm_bindgen_futures::spawn_local(async move {
let payload = QrPayload { user_id };
match Request::post("/api/generate_qr")
.json(&payload)
.unwrap()
.send()
.await
{
Ok(response) if response.ok() => {
if let Ok(data) = response.json::<QrResponse>().await {
// Point scanned link to the public login endpoint with the token
let target_url = format!(
"{}/api/qr_login?token={}",
std::env::var("ORIGIN")
.ok()
.unwrap_or("http://localhost:8000".to_string()),
data.raw_token
);
match qrcode_generator::to_png_to_vec(
target_url,
qrcode_generator::QrCodeEcc::Medium,
250,
) {
Ok(png_bytes) => {
let base64_png = format!(
"data:image/png;base64,{}",
STANDARD.encode(&png_bytes)
);
qr_image_data.set(Some(base64_png));
error_message.set(None);
}
Err(_) => {
error_message
.set(Some("Failed to render QR graphic.".to_string()));
}
}
}
}
_ => {
error_message.set(Some(
"Failed to generate token. Are you logged in as an admin?".to_string(),
));
}
}
});
})
};
html! {
<div style="text-align: center; padding: 15px; border: 1px dashed #ccc; border-radius: 6px;">
<h4>{"Permanent Login Access"}</h4>
<p style="font-size: 0.85em; color: #555;">
{ format!("Generate a permanent bypass login QR code for User #{}", user_id) }
</p>
<button
onclick={on_generate_click}
style="padding: 8px 16px; background-color: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;"
>
{"Generate QR Code"}
</button>
{ if let Some(ref err) = *error_message {
html! { <p style="color: #dc3545; margin-top: 10px; font-size: 0.9em;">{err}</p> }
} else {
html! {}
}}
{ if let Some(ref qr_src) = *qr_image_data {
html! {
<div style="margin-top: 15px;">
<img src={qr_src.clone()} alt="Login QR Code" style="border: 1px solid #ddd; padding: 8px; background: white;" />
<p style="font-size: 0.8em; color: #dc3545; font-weight: bold; margin-top: 8px;">
{"Warn the user to keep this QR code secure!"}
</p>
</div>
}
} else {
html! {}
}}
</div>
}
}
+3 -2
View File
@@ -185,7 +185,7 @@ pub fn register_component() -> Html {
Ok(response) => {
let text = response.text().await.unwrap_or_else(|_| "Unbekannt".into());
status.set(Some(text));
},
}
Err(err) => status.set(Some(format!("Netzwerkfehler: {}", err))),
}
});
@@ -688,7 +688,8 @@ pub fn user_by_id_component(props: &UserProps) -> Html {
<p><strong>{ "Ist Admin: " }</strong>{ u.is_admin }</p>
</div>
<h1>{ format!("User #{}", u.id) }</h1>
<crate::pages::qr::QrManager user_id={u.id}/>
<form onsubmit={onsubmit}>
<div>
<label>{ "Vorname" }