Make secret more logsafe, implement add and edit for links
This commit is contained in:
@@ -3,8 +3,11 @@ add-link = Link hinzufügen
|
||||
invite-user = Benutzer einladen
|
||||
list-users = Liste der Benutzer
|
||||
welcome-user = Herzlich willkommen {$username}
|
||||
welcome = Herzlich willkommen
|
||||
logout = Abmelden
|
||||
login = Login
|
||||
yes = Ja
|
||||
no = Nein
|
||||
|
||||
not-found = Dieser Link existiert nicht, oder wurde gelöscht.
|
||||
|
||||
@@ -17,6 +20,7 @@ link-code = Link Code
|
||||
shortlink = Shortlink
|
||||
qr-code = QR-code
|
||||
search-placeholder = Filtern nach...
|
||||
really-delete = Wollen Sie {$code} wirklich löschen?
|
||||
|
||||
danger-zone = Achtung!
|
||||
danger-zone-text = Verändern Sie den Code von bereits veröffentlichten Links nicht. Sollte es dennoch geschehen werden veröffentlichte links unbenutzbar. Wird das Linkziel verändert, so zeigen auch die bereits veröffentlichten Links auf das neue Ziel.
|
||||
|
||||
@@ -3,8 +3,11 @@ add-link = Add a new link
|
||||
invite-user = Invite a new user
|
||||
list-users = List of existing users
|
||||
welcome-user = Welcome {$username}
|
||||
welcome = Welcome
|
||||
logout = Logout
|
||||
login = Login
|
||||
yes = Ja
|
||||
no = Nein
|
||||
|
||||
not-found = This Link has not been found or has been deleted
|
||||
|
||||
@@ -16,6 +19,7 @@ link-target = Link target
|
||||
link-code = Link code
|
||||
shortlink = Shortlink
|
||||
search-placeholder = Filter according to...
|
||||
really-delete = Do you really want to delete {$code}?
|
||||
|
||||
danger-zone = Danger Zone!
|
||||
danger-zone-text = Do not change the code of links that are published. If you do so the published links will become invalid! If you change the target the published links will point to the new target.
|
||||
|
||||
+34
-1
@@ -5,6 +5,7 @@ pub mod pages;
|
||||
use pages::list_links;
|
||||
use pages::list_users;
|
||||
use seed::{div, log, prelude::*, App, Url, C};
|
||||
use shared::datatypes::User;
|
||||
|
||||
use crate::i18n::{I18n, Lang};
|
||||
|
||||
@@ -14,6 +15,7 @@ use crate::i18n::{I18n, Lang};
|
||||
|
||||
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
orders.subscribe(Msg::UrlChanged);
|
||||
orders.send_msg(Msg::GetLoggedUser);
|
||||
|
||||
log!(url);
|
||||
|
||||
@@ -24,6 +26,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
base_url: Url::new().add_path_part("app"),
|
||||
page: Page::init(url, orders, lang.clone()),
|
||||
i18n: lang,
|
||||
user: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +40,7 @@ struct Model {
|
||||
base_url: Url,
|
||||
page: Page,
|
||||
i18n: i18n::I18n,
|
||||
user: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -76,6 +80,8 @@ pub enum Msg {
|
||||
UrlChanged(subs::UrlChanged),
|
||||
ListLinks(list_links::Msg),
|
||||
ListUsers(list_users::Msg),
|
||||
GetLoggedUser,
|
||||
UserReceived(User),
|
||||
NoMessage,
|
||||
}
|
||||
|
||||
@@ -97,6 +103,29 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
}
|
||||
Msg::NoMessage => (),
|
||||
Msg::GetLoggedUser => {
|
||||
orders.skip(); // No need to rerender/ complicated way to move into the closure
|
||||
orders.perform_cmd(async {
|
||||
let response = fetch(
|
||||
Request::new("/admin/json/get_logged_user/")
|
||||
.method(Method::Post)
|
||||
.json(&())
|
||||
.expect("serialization failed"),
|
||||
)
|
||||
.await
|
||||
.expect("HTTP request failed");
|
||||
|
||||
let user: User = response
|
||||
.check_status() // ensure we've got 2xx status
|
||||
.expect("status check failed")
|
||||
.json()
|
||||
.await
|
||||
.expect("deserialization failed");
|
||||
|
||||
Msg::UserReceived(user)
|
||||
});
|
||||
}
|
||||
Msg::UserReceived(user) => model.user = Some(user),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +169,10 @@ impl<'a> Urls<'a> {
|
||||
self.base_url().add_path_part("list_links")
|
||||
}
|
||||
#[must_use]
|
||||
pub fn create_link(self) -> Url {
|
||||
self.list_links().add_path_part("create_link")
|
||||
}
|
||||
#[must_use]
|
||||
pub fn list_users(self) -> Url {
|
||||
self.base_url().add_path_part("list_users")
|
||||
}
|
||||
@@ -156,7 +189,7 @@ impl<'a> Urls<'a> {
|
||||
fn view(model: &Model) -> Node<Msg> {
|
||||
div![
|
||||
C!["page"],
|
||||
navigation::navigation(&model.i18n, &model.base_url,),
|
||||
navigation::navigation(&model.i18n, &model.base_url, &model.user),
|
||||
view_content(&model.page, &model.base_url),
|
||||
]
|
||||
}
|
||||
|
||||
+25
-20
@@ -1,29 +1,34 @@
|
||||
use fluent::fluent_args;
|
||||
use seed::{a, attrs, div, li, nav, ol, prelude::*, Url};
|
||||
use shared::datatypes::User;
|
||||
|
||||
use crate::{i18n::I18n, Msg};
|
||||
#[must_use]
|
||||
pub fn navigation(i18n: &I18n, base_url: &Url) -> Node<Msg> {
|
||||
let username = fluent_args![ "username" => "enaut"];
|
||||
macro_rules! t {
|
||||
{ $key:expr } => {
|
||||
{
|
||||
i18n.translate($key, None)
|
||||
}
|
||||
};
|
||||
{ $key:expr, $args:expr } => {
|
||||
{
|
||||
i18n.translate($key, Some(&$args))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub fn navigation(i18n: &I18n, base_url: &Url, user: &Option<User>) -> Node<Msg> {
|
||||
let t = move |key: &str| i18n.translate(key, None);
|
||||
let welcome = if let Some(user) = user {
|
||||
i18n.translate(
|
||||
"welcome-user",
|
||||
Some(&fluent_args![ "username" => user.username.clone()]),
|
||||
)
|
||||
} else {
|
||||
t("welcome")
|
||||
};
|
||||
nav![
|
||||
ol![
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).list_links()},
|
||||
t!("list-links"),
|
||||
t("list-links"),
|
||||
],],
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).create_link()},
|
||||
ev(Ev::Click, |_| Msg::ListLinks(
|
||||
super::pages::list_links::Msg::Edit(
|
||||
super::pages::list_links::EditMsg::CreateNewLink
|
||||
)
|
||||
)),
|
||||
t("add-link"),
|
||||
],],
|
||||
li![a![ev(Ev::Click, |_| Msg::NoMessage), t!("add-link"),],],
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).create_user()},
|
||||
ev(Ev::Click, |_| Msg::ListUsers(
|
||||
@@ -31,19 +36,19 @@ pub fn navigation(i18n: &I18n, base_url: &Url) -> Node<Msg> {
|
||||
super::pages::list_users::UserEditMsg::CreateNewUser
|
||||
)
|
||||
)),
|
||||
t!("invite-user"),
|
||||
t("invite-user"),
|
||||
],],
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).list_users()},
|
||||
t!("list-users"),
|
||||
t("list-users"),
|
||||
],],
|
||||
],
|
||||
ol![
|
||||
li![div![t!("welcome-user", username)]],
|
||||
li![div![welcome]],
|
||||
li![a![
|
||||
attrs! {At::Href => "#"},
|
||||
ev(Ev::Click, |_| Msg::NoMessage),
|
||||
t!("logout"),
|
||||
t("logout"),
|
||||
]]
|
||||
]
|
||||
]
|
||||
|
||||
+412
-60
@@ -1,25 +1,50 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use enum_map::EnumMap;
|
||||
use seed::{a, attrs, button, h1, input, log, prelude::*, section, table, td, th, tr, Url, C};
|
||||
use fluent::fluent_args;
|
||||
use seed::{
|
||||
a, attrs, button, div, h1, img, input, log, prelude::*, section, span, table, td, th, tr, Url,
|
||||
C,
|
||||
};
|
||||
|
||||
use shared::{
|
||||
apirequests::general::Ordering,
|
||||
apirequests::{
|
||||
general::Operation,
|
||||
links::{LinkOverviewColumns, LinkRequestForm},
|
||||
general::{EditMode, Message, Operation, Status},
|
||||
links::{LinkDelta, LinkOverviewColumns, LinkRequestForm},
|
||||
},
|
||||
datatypes::FullLink,
|
||||
};
|
||||
|
||||
use crate::i18n::I18n;
|
||||
|
||||
pub fn init(_: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
|
||||
orders.send_msg(Msg::Fetch);
|
||||
/// Unwrap a result and return it's content, or return from the function with another expression.
|
||||
macro_rules! unwrap_or_return {
|
||||
( $e:expr, $result:expr) => {
|
||||
match $e {
|
||||
Ok(x) => x,
|
||||
Err(_) => return $result,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
|
||||
log!(url);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
let edit_link = match url.next_path_part() {
|
||||
Some("create_link") => Some(RefCell::new(LinkDelta::default())),
|
||||
None | Some(_) => None,
|
||||
};
|
||||
log!(edit_link);
|
||||
|
||||
Model {
|
||||
links: Vec::new(),
|
||||
i18n,
|
||||
formconfig: LinkRequestForm::default(),
|
||||
inputs: EnumMap::default(),
|
||||
edit_link,
|
||||
last_message: None,
|
||||
question: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +54,9 @@ pub struct Model {
|
||||
i18n: I18n,
|
||||
formconfig: LinkRequestForm,
|
||||
inputs: EnumMap<LinkOverviewColumns, FilterInput>,
|
||||
edit_link: Option<RefCell<LinkDelta>>,
|
||||
last_message: Option<Status>,
|
||||
question: Option<EditMsg>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
@@ -38,6 +66,14 @@ struct FilterInput {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Msg {
|
||||
Query(QueryMsg),
|
||||
Edit(EditMsg),
|
||||
ClearAll,
|
||||
SetMessage(String),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum QueryMsg {
|
||||
Fetch,
|
||||
OrderBy(LinkOverviewColumns),
|
||||
Received(Vec<FullLink>),
|
||||
@@ -46,36 +82,51 @@ pub enum Msg {
|
||||
TargetFilterChanged(String),
|
||||
AuthorFilterChanged(String),
|
||||
}
|
||||
/// All the messages on link editing
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EditMsg {
|
||||
EditSelected(LinkDelta),
|
||||
CreateNewLink,
|
||||
Created(Status),
|
||||
EditCodeChanged(String),
|
||||
EditDescriptionChanged(String),
|
||||
EditTargetChanged(String),
|
||||
MayDeleteSelected(LinkDelta),
|
||||
DeleteSelected(LinkDelta),
|
||||
SaveLink,
|
||||
FailedToCreateLink,
|
||||
FailedToDeleteLink,
|
||||
DeletedLink(Status),
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// Sould only panic on bugs.
|
||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
Msg::Fetch => {
|
||||
orders.skip(); // No need to rerender
|
||||
let data = model.formconfig.clone(); // complicated way to move into the closure
|
||||
orders.perform_cmd(async {
|
||||
let data = data;
|
||||
let response = fetch(
|
||||
Request::new("/admin/json/list_links/")
|
||||
.method(Method::Post)
|
||||
.json(&data)
|
||||
.expect("serialization failed"),
|
||||
)
|
||||
.await
|
||||
.expect("HTTP request failed");
|
||||
|
||||
let user: Vec<FullLink> = response
|
||||
.check_status() // ensure we've got 2xx status
|
||||
.expect("status check failed")
|
||||
.json()
|
||||
.await
|
||||
.expect("deserialization failed");
|
||||
|
||||
Msg::Received(user)
|
||||
});
|
||||
Msg::Query(msg) => process_query_messages(msg, model, orders),
|
||||
Msg::Edit(msg) => process_edit_messages(msg, model, orders),
|
||||
Msg::ClearAll => {
|
||||
model.edit_link = None;
|
||||
model.last_message = None;
|
||||
model.question = None;
|
||||
}
|
||||
Msg::OrderBy(column) => {
|
||||
Msg::SetMessage(msg) => {
|
||||
model.edit_link = None;
|
||||
model.question = None;
|
||||
model.last_message = Some(Status::Error(Message { message: msg }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// Sould only panic on bugs.
|
||||
pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
QueryMsg::Fetch => {
|
||||
orders.skip(); // No need to rerender
|
||||
load_links(model, orders)
|
||||
}
|
||||
QueryMsg::OrderBy(column) => {
|
||||
model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
|
||||
|| {
|
||||
Some(Operation {
|
||||
@@ -94,7 +145,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
})
|
||||
},
|
||||
);
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
|
||||
model.links.sort_by(match column {
|
||||
LinkOverviewColumns::Code => {
|
||||
@@ -114,44 +165,231 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
})
|
||||
}
|
||||
Msg::Received(response) => {
|
||||
QueryMsg::Received(response) => {
|
||||
model.links = response;
|
||||
}
|
||||
Msg::CodeFilterChanged(s) => {
|
||||
QueryMsg::CodeFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Code].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
Msg::DescriptionFilterChanged(s) => {
|
||||
QueryMsg::DescriptionFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Description].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
Msg::TargetFilterChanged(s) => {
|
||||
QueryMsg::TargetFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Target].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
Msg::AuthorFilterChanged(s) => {
|
||||
QueryMsg::AuthorFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Author].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
}
|
||||
}
|
||||
fn load_links(model: &Model, orders: &mut impl Orders<Msg>) {
|
||||
let data = model.formconfig.clone(); // complicated way to move into the closure
|
||||
orders.perform_cmd(async {
|
||||
let data = data;
|
||||
let request = unwrap_or_return!(
|
||||
Request::new("/admin/json/list_links/")
|
||||
.method(Method::Post)
|
||||
.json(&data),
|
||||
Msg::SetMessage("Failed to parse data".to_string())
|
||||
);
|
||||
let response = unwrap_or_return!(
|
||||
fetch(request).await,
|
||||
Msg::SetMessage("Failed to send data".to_string())
|
||||
);
|
||||
|
||||
let response = unwrap_or_return!(
|
||||
response.check_status(),
|
||||
Msg::SetMessage("Wrong response code".to_string())
|
||||
);
|
||||
|
||||
let links: Vec<FullLink> = unwrap_or_return!(
|
||||
response.json().await,
|
||||
Msg::SetMessage("Invalid response".to_string())
|
||||
);
|
||||
|
||||
Msg::Query(QueryMsg::Received(links))
|
||||
});
|
||||
}
|
||||
|
||||
pub fn process_edit_messages(msg: EditMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
EditMsg::EditSelected(link) => {
|
||||
log!("Editing link: ", link);
|
||||
model.last_message = None;
|
||||
model.edit_link = Some(RefCell::new(link))
|
||||
}
|
||||
EditMsg::CreateNewLink => {
|
||||
log!("Create new link!");
|
||||
model.edit_link = Some(RefCell::new(LinkDelta::default()))
|
||||
}
|
||||
EditMsg::Created(success_msg) => {
|
||||
model.last_message = Some(success_msg);
|
||||
model.edit_link = None;
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
EditMsg::EditCodeChanged(s) => {
|
||||
if let Some(ref le) = model.edit_link {
|
||||
le.try_borrow_mut().expect("Failed to borrow mutably").code = s;
|
||||
}
|
||||
}
|
||||
EditMsg::EditDescriptionChanged(s) => {
|
||||
if let Some(ref le) = model.edit_link {
|
||||
le.try_borrow_mut().expect("Failed to borrow mutably").title = s;
|
||||
}
|
||||
}
|
||||
EditMsg::EditTargetChanged(s) => {
|
||||
if let Some(ref le) = model.edit_link {
|
||||
le.try_borrow_mut()
|
||||
.expect("Failed to borrow mutably")
|
||||
.target = s;
|
||||
}
|
||||
}
|
||||
EditMsg::SaveLink => {
|
||||
save_link(model, orders);
|
||||
}
|
||||
EditMsg::FailedToCreateLink => {
|
||||
log!("Failed to create Link");
|
||||
}
|
||||
link @ EditMsg::MayDeleteSelected(..) => {
|
||||
log!("Deleting link: ", link);
|
||||
model.last_message = None;
|
||||
model.edit_link = None;
|
||||
model.question = Some(link)
|
||||
}
|
||||
EditMsg::DeleteSelected(link) => {
|
||||
orders.perform_cmd(async {
|
||||
let data = link;
|
||||
let response = unwrap_or_return!(
|
||||
fetch(
|
||||
Request::new("/admin/json/delete_link/")
|
||||
.method(Method::Post)
|
||||
.json(&data)
|
||||
.expect("serialization failed"),
|
||||
)
|
||||
.await,
|
||||
Msg::Edit(EditMsg::FailedToDeleteLink)
|
||||
);
|
||||
|
||||
let response = unwrap_or_return!(
|
||||
response.check_status(),
|
||||
Msg::SetMessage("Wrong response code!".to_string())
|
||||
);
|
||||
let message: Status = unwrap_or_return!(
|
||||
response.json().await,
|
||||
Msg::SetMessage(
|
||||
"Failed to parse the response the link might be deleted however!"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
|
||||
Msg::Edit(EditMsg::DeletedLink(message))
|
||||
});
|
||||
}
|
||||
EditMsg::FailedToDeleteLink => {
|
||||
log!("Failed to delete Link");
|
||||
}
|
||||
EditMsg::DeletedLink(message) => {
|
||||
log!("Deleted link", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save_link(model: &Model, orders: &mut impl Orders<Msg>) {
|
||||
let data = model
|
||||
.edit_link
|
||||
.as_ref()
|
||||
.expect("should exist!")
|
||||
.borrow()
|
||||
.clone();
|
||||
orders.perform_cmd(async {
|
||||
let data = data;
|
||||
let request = unwrap_or_return!(
|
||||
Request::new(match data.edit {
|
||||
EditMode::Create => "/admin/json/create_link/",
|
||||
EditMode::Edit => "/admin/json/edit_link/",
|
||||
})
|
||||
.method(Method::Post)
|
||||
.json(&data),
|
||||
Msg::SetMessage("Failed to encode the link!".to_string())
|
||||
);
|
||||
let response =
|
||||
unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToCreateLink));
|
||||
|
||||
log!(response);
|
||||
let response = unwrap_or_return!(
|
||||
response.check_status(),
|
||||
Msg::SetMessage("Wrong response code".to_string())
|
||||
);
|
||||
|
||||
let message: Status = unwrap_or_return!(
|
||||
response.json().await,
|
||||
Msg::SetMessage("Invalid response!".to_string())
|
||||
);
|
||||
|
||||
Msg::Edit(EditMsg::Created(message))
|
||||
});
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// # Panics
|
||||
/// Sould only panic on bugs.
|
||||
pub fn view(model: &Model) -> Node<Msg> {
|
||||
let lang = model.i18n.clone();
|
||||
let lang = &model.i18n.clone();
|
||||
let t = move |key: &str| lang.translate(key, None);
|
||||
section![
|
||||
h1!("List Links Page from list_links"),
|
||||
if let Some(message) = &model.last_message {
|
||||
div![
|
||||
C!["message", "center"],
|
||||
div![
|
||||
C!["closebutton"],
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
match message {
|
||||
Status::Success(m) | Status::Error(m) => &m.message,
|
||||
}
|
||||
]
|
||||
} else {
|
||||
section![]
|
||||
},
|
||||
if let Some(question) = &model.question {
|
||||
div![
|
||||
C!["message", "center"],
|
||||
div![
|
||||
C!["closebutton"],
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
if let EditMsg::MayDeleteSelected(l) = question.clone() {
|
||||
div![
|
||||
lang.translate(
|
||||
"really-delete",
|
||||
Some(&fluent_args!["code" => l.code.clone()])
|
||||
),
|
||||
a![t("no"), C!["button"], ev(Ev::Click, |_| Msg::ClearAll)],
|
||||
a![
|
||||
t("yes"),
|
||||
C!["button"],
|
||||
ev(Ev::Click, move |_| Msg::Edit(EditMsg::DeleteSelected(l)))
|
||||
]
|
||||
]
|
||||
} else {
|
||||
span!()
|
||||
}
|
||||
]
|
||||
} else {
|
||||
section![]
|
||||
},
|
||||
table![
|
||||
// Add the headlines
|
||||
view_link_table_head(&t),
|
||||
@@ -160,34 +398,51 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
// Add all the content lines
|
||||
model.links.iter().map(view_link)
|
||||
],
|
||||
button![ev(Ev::Click, |_| Msg::Fetch), "Fetch links"]
|
||||
button![
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::Fetch)),
|
||||
"Fetch links"
|
||||
],
|
||||
if let Some(l) = &model.edit_link {
|
||||
edit_or_create_link(l, t)
|
||||
} else {
|
||||
section!()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fn view_link_table_head<F: Fn(&str) -> String>(t: F) -> Node<Msg> {
|
||||
tr![
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Code)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Code
|
||||
))),
|
||||
t("link-code")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Description
|
||||
)),
|
||||
))),
|
||||
t("link-description")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Target)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Target
|
||||
))),
|
||||
t("link-target")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Author)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Author
|
||||
))),
|
||||
t("username")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Statistics)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Statistics
|
||||
))),
|
||||
t("statistics")
|
||||
]
|
||||
],
|
||||
th![]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -200,7 +455,7 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::CodeFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(QueryMsg::CodeFilterChanged(s))),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Code].filter_input),
|
||||
]],
|
||||
td![input![
|
||||
@@ -210,7 +465,9 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::DescriptionFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(
|
||||
QueryMsg::DescriptionFilterChanged(s)
|
||||
)),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Description].filter_input),
|
||||
]],
|
||||
td![input![
|
||||
@@ -220,7 +477,7 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::TargetFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(QueryMsg::TargetFilterChanged(s))),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Target].filter_input),
|
||||
]],
|
||||
td![input![
|
||||
@@ -230,19 +487,114 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::AuthorFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(QueryMsg::AuthorFilterChanged(s))),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Author].filter_input),
|
||||
]],
|
||||
td![]
|
||||
td![],
|
||||
td![],
|
||||
]
|
||||
}
|
||||
|
||||
fn view_link(l: &FullLink) -> Node<Msg> {
|
||||
// Ugly hack
|
||||
let link = LinkDelta::from(l.clone());
|
||||
let link2 = LinkDelta::from(l.clone());
|
||||
let link3 = LinkDelta::from(l.clone());
|
||||
let link4 = LinkDelta::from(l.clone());
|
||||
let link5 = LinkDelta::from(l.clone());
|
||||
tr![
|
||||
td![&l.link.code],
|
||||
td![&l.link.title],
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link))),
|
||||
&l.link.code
|
||||
]
|
||||
},
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link2))),
|
||||
&l.link.title
|
||||
]
|
||||
},
|
||||
td![a![attrs![At::Href => &l.link.target], &l.link.target]],
|
||||
td![&l.user.username],
|
||||
td![&l.clicks.number]
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link3))),
|
||||
&l.user.username
|
||||
]
|
||||
},
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link4))),
|
||||
&l.clicks.number
|
||||
]
|
||||
},
|
||||
{
|
||||
td![img![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::MayDeleteSelected(link5))),
|
||||
C!["trashicon"],
|
||||
attrs!(At::Src => "/static/trash.svg")
|
||||
]]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn edit_or_create_link<F: Fn(&str) -> String>(l: &RefCell<LinkDelta>, t: F) -> Node<Msg> {
|
||||
let link = l.borrow();
|
||||
div![
|
||||
C!["editdialog", "center"],
|
||||
div![
|
||||
C!["closebutton"],
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
h1![match &link.edit {
|
||||
EditMode::Edit => t("edit-link"),
|
||||
EditMode::Create => t("create-link"),
|
||||
}],
|
||||
table![
|
||||
tr![
|
||||
th![t("link-description")],
|
||||
td![input![
|
||||
attrs! {
|
||||
At::Value => &link.title,
|
||||
At::Type => "text",
|
||||
At::Placeholder => t("link-description")
|
||||
},
|
||||
input_ev(Ev::Input, |s| {
|
||||
Msg::Edit(EditMsg::EditDescriptionChanged(s))
|
||||
}),
|
||||
]]
|
||||
],
|
||||
tr![
|
||||
th![t("link-target")],
|
||||
td![input![
|
||||
attrs! {
|
||||
At::Value => &link.target,
|
||||
At::Type => "text",
|
||||
At::Placeholder => t("link-target")
|
||||
},
|
||||
input_ev(Ev::Input, |s| { Msg::Edit(EditMsg::EditTargetChanged(s)) }),
|
||||
]]
|
||||
],
|
||||
tr![
|
||||
th![t("link-code")],
|
||||
td![input![
|
||||
attrs! {
|
||||
At::Value => &link.code,
|
||||
At::Type => "text",
|
||||
At::Placeholder => t("password")
|
||||
},
|
||||
input_ev(Ev::Input, |s| { Msg::Edit(EditMsg::EditCodeChanged(s)) }),
|
||||
],]
|
||||
]
|
||||
],
|
||||
a![
|
||||
match &link.edit {
|
||||
EditMode::Edit => t("edit-link"),
|
||||
EditMode::Create => t("create-link"),
|
||||
},
|
||||
C!["button"],
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::SaveLink))
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use seed::{
|
||||
use shared::{
|
||||
apirequests::general::{Operation, Ordering},
|
||||
apirequests::{
|
||||
general::{EditMode, SuccessMessage},
|
||||
general::{EditMode, Status},
|
||||
users::{UserDelta, UserOverviewColumns, UserRequestForm},
|
||||
},
|
||||
datatypes::User,
|
||||
@@ -37,7 +37,7 @@ pub struct Model {
|
||||
formconfig: UserRequestForm,
|
||||
inputs: EnumMap<UserOverviewColumns, FilterInput>,
|
||||
user_edit: Option<RefCell<UserDelta>>,
|
||||
last_message: Option<SuccessMessage>,
|
||||
last_message: Option<Status>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
@@ -68,7 +68,7 @@ pub enum UserQueryMsg {
|
||||
pub enum UserEditMsg {
|
||||
EditUserSelected(UserDelta),
|
||||
CreateNewUser,
|
||||
UserCreated(SuccessMessage),
|
||||
UserCreated(Status),
|
||||
EditUsernameChanged(String),
|
||||
EditEmailChanged(String),
|
||||
EditPasswordChanged(String),
|
||||
@@ -236,7 +236,7 @@ pub fn process_user_edit_messages(
|
||||
Err(_) => return Msg::Edit(UserEditMsg::FailedToCreateUser),
|
||||
};
|
||||
|
||||
let message: SuccessMessage = response
|
||||
let message: Status = response
|
||||
.check_status() // ensure we've got 2xx status
|
||||
.expect("status check failed")
|
||||
.json()
|
||||
@@ -268,7 +268,6 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
keyboard_ev(Ev::KeyDown, |keyboard_event| {
|
||||
IF!(keyboard_event.key() == "Escape" => Msg::ClearAll)
|
||||
}),
|
||||
h1!("List Users Page from list_users"),
|
||||
if let Some(message) = &model.last_message {
|
||||
div![
|
||||
C!["message", "center"],
|
||||
@@ -277,7 +276,11 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
&message.message
|
||||
match message {
|
||||
Status::Success(m) | Status::Error(m) => {
|
||||
&m.message
|
||||
}
|
||||
}
|
||||
]
|
||||
} else {
|
||||
section![]
|
||||
|
||||
Reference in New Issue
Block a user