make privileges more defining for UI

This commit is contained in:
Dietrich 2021-06-15 12:59:56 +02:00 committed by Franz Dietrich
parent eee0a8dba2
commit f361a13c91
9 changed files with 150 additions and 87 deletions

View File

@ -335,7 +335,7 @@ fn view(model: &Model) -> Node<Msg> {
match model.user { match model.user {
Loadable::Data(Some(ref user)) => div![ Loadable::Data(Some(ref user)) => div![
navigation::navigation(&model.i18n, &model.location.base_url, user), navigation::navigation(&model.i18n, &model.location.base_url, user),
view_content(&model.page, &model.location.base_url) view_content(&model.page, &model.location.base_url, user)
], ],
Loadable::Data(None) => view_login(&model.i18n, model), Loadable::Data(None) => view_login(&model.i18n, model),
Loadable::Loading => div![C!("lds-ellipsis"), div!(), div!(), div!(), div!()], Loadable::Loading => div![C!("lds-ellipsis"), div!(), div!(), div!(), div!()],
@ -344,12 +344,12 @@ fn view(model: &Model) -> Node<Msg> {
} }
/// Render the subpages. /// Render the subpages.
fn view_content(page: &Page, url: &Url) -> Node<Msg> { fn view_content(page: &Page, url: &Url, user: &User) -> Node<Msg> {
div![ div![
C!["container"], C!["container"],
match page { match page {
Page::Home(model) => pages::list_links::view(model).map_msg(Msg::ListLinks), Page::Home(model) => pages::list_links::view(model, user).map_msg(Msg::ListLinks),
Page::ListUsers(model) => pages::list_users::view(model).map_msg(Msg::ListUsers), Page::ListUsers(model) => pages::list_users::view(model, user).map_msg(Msg::ListUsers),
Page::NotFound => div![div![url.to_string()], "Page not found!"], Page::NotFound => div![div![url.to_string()], "Page not found!"],
} }
] ]

View File

@ -47,16 +47,16 @@ pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
) )
)), )),
t("invite-user"), t("invite-user"),
],], ],]
// A button to list all users
li![a![
attrs! {At::Href => crate::Urls::new(base_url).list_users()},
t("list-users"),
],],
] ]
} else { } else {
nodes!() nodes!()
}, },
// A button to list all users
li![a![
attrs! {At::Href => crate::Urls::new(base_url).list_users()},
t("list-users"),
],],
], ],
ol![ ol![
li![div![ li![div![

View File

@ -13,7 +13,7 @@ use shared::{
general::{EditMode, Message, Operation, Status}, general::{EditMode, Message, Operation, Status},
links::{LinkDelta, LinkOverviewColumns, LinkRequestForm}, links::{LinkDelta, LinkOverviewColumns, LinkRequestForm},
}, },
datatypes::{FullLink, Lang, Loadable}, datatypes::{FullLink, Lang, Loadable, User},
}; };
use crate::{get_host, i18n::I18n, unwrap_or_return}; use crate::{get_host, i18n::I18n, unwrap_or_return};
@ -456,7 +456,7 @@ fn delete_link(link_delta: LinkDelta, orders: &mut impl Orders<Msg>) {
/// * questions /// * questions
/// * the table of links including sorting and searching /// * the table of links including sorting and searching
#[must_use] #[must_use]
pub fn view(model: &Model) -> Node<Msg> { pub fn view(model: &Model, logged_in_user: &User) -> Node<Msg> {
let lang = &model.i18n.clone(); let lang = &model.i18n.clone();
// shortcut for translating // shortcut for translating
let t = move |key: &str| lang.translate(key, None); let t = move |key: &str| lang.translate(key, None);
@ -498,7 +498,7 @@ pub fn view(model: &Model) -> Node<Msg> {
// Add filter fields right below the headlines // Add filter fields right below the headlines
view_link_table_filter_input(model, &t), view_link_table_filter_input(model, &t),
// Add all the content lines // Add all the content lines
model.links.iter().map(view_link) model.links.iter().map(|l| { view_link(l, logged_in_user) })
], ],
// A fetch button - this should not be needed and will be removed in future. // A fetch button - this should not be needed and will be removed in future.
button![ button![
@ -541,6 +541,7 @@ fn view_link_table_head<F: Fn(&str) -> String>(t: F) -> Node<Msg> {
))), ))),
t("statistics") t("statistics")
], ],
th![],
th![] th![]
] ]
} }
@ -593,43 +594,34 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
// statistics and the delete column cannot be filtered // statistics and the delete column cannot be filtered
td![], td![],
td![], td![],
td![],
] ]
} }
/// display a single table row containing one link /// display a single table row containing one link
fn view_link(l: &FullLink) -> Node<Msg> { fn view_link(l: &FullLink, logged_in_user: &User) -> Node<Msg> {
// Ugly hack - this is needed to be able to move the l into the closures... l.clone() in place does not work. use shared::apirequests::users::Role;
let link = LinkDelta::from(l.clone()); macro_rules! event_or_not {
let link2 = LinkDelta::from(l.clone()); ( $link:expr, $user:expr, $content:expr) => {
let link3 = LinkDelta::from(l.clone()); if $user.role == Role::Admin
let link4 = LinkDelta::from(l.clone()); || ($user.role == Role::Regular) && $link.user.id == $user.id
let link5 = LinkDelta::from(l.clone()); {
let link = LinkDelta::from($link.clone());
td![
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link))),
$content
]
} else {
td![$content]
}
};
}
tr![ tr![
{ event_or_not!(l, logged_in_user, &l.link.code),
td![ event_or_not!(l, logged_in_user, &l.link.title),
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link))), event_or_not!(l, logged_in_user, &l.link.target),
&l.link.code event_or_not!(l, logged_in_user, &l.user.username),
] event_or_not!(l, logged_in_user, &l.clicks.number),
},
{
td![
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link2))),
&l.link.title
]
},
td![a![attrs![At::Href => &l.link.target], &l.link.target]],
{
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![ td![
C!["table_qr"], C!["table_qr"],
@ -639,12 +631,16 @@ fn view_link(l: &FullLink) -> Node<Msg> {
] ]
] ]
}, },
if logged_in_user.role == Role::Admin
|| (logged_in_user.role == Role::Regular) && l.user.id == logged_in_user.id
{ {
td![img![ let link = LinkDelta::from(l.clone());
ev(Ev::Click, |_| Msg::Edit(EditMsg::MayDeleteSelected(link5))), td![
C!["trashicon"], ev(Ev::Click, |_| Msg::Edit(EditMsg::MayDeleteSelected(link))),
attrs!(At::Src => "/static/trash.svg") img![C!["trashicon"], attrs!(At::Src => "/static/trash.svg")]
]] ]
} else {
td![]
}, },
] ]
} }

View File

@ -241,11 +241,41 @@ pub fn process_user_edit_messages(
model.user_edit = None; model.user_edit = None;
orders.send_msg(Msg::Query(UserQueryMsg::Fetch)); orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
} }
UserEditMsg::MakeAdmin(user) => todo!(), UserEditMsg::MakeAdmin(user) => update_privileges(user, orders),
UserEditMsg::MakeRegular(user) => todo!(), UserEditMsg::MakeRegular(user) => update_privileges(user, orders),
} }
} }
fn update_privileges(user: UserDelta, orders: &mut impl Orders<Msg>) {
orders.perform_cmd(async {
let data = user;
// create the request
let request = unwrap_or_return!(
Request::new("/admin/json/update_privileges/")
.method(Method::Post)
.json(&data),
Msg::Edit(UserEditMsg::FailedToCreateUser)
);
// perform the request and get the response
let response = unwrap_or_return!(
fetch(request).await,
Msg::Edit(UserEditMsg::FailedToCreateUser)
);
// check for the status
let response = unwrap_or_return!(
response.check_status(),
Msg::Edit(UserEditMsg::FailedToCreateUser)
);
// deserialize the response
let message: Status = unwrap_or_return!(
response.json().await,
Msg::Edit(UserEditMsg::FailedToCreateUser)
);
Msg::Edit(UserEditMsg::UserCreated(message))
});
}
fn save_user(user: UserDelta, orders: &mut impl Orders<Msg>) { fn save_user(user: UserDelta, orders: &mut impl Orders<Msg>) {
orders.perform_cmd(async { orders.perform_cmd(async {
let data = user; let data = user;
@ -281,7 +311,7 @@ fn save_user(user: UserDelta, orders: &mut impl Orders<Msg>) {
#[must_use] #[must_use]
/// View the users page. /// View the users page.
pub fn view(model: &Model) -> Node<Msg> { pub fn view(model: &Model, logged_in_user: &User) -> Node<Msg> {
let lang = model.i18n.clone(); let lang = model.i18n.clone();
// shortcut for easier translations // shortcut for easier translations
let t = move |key: &str| lang.translate(key, None); let t = move |key: &str| lang.translate(key, None);
@ -311,7 +341,10 @@ pub fn view(model: &Model) -> Node<Msg> {
// Add filter fields right below the headlines // Add filter fields right below the headlines
view_user_table_filter_input(model, &t), view_user_table_filter_input(model, &t),
// Add all the users one line for each // Add all the users one line for each
model.users.iter().map(|u| { view_user(u, &t) }) model
.users
.iter()
.map(|u| { view_user(u, logged_in_user, &t) })
], ],
// A refresh button. This will be removed in future versions. // A refresh button. This will be removed in future versions.
button![ button![
@ -393,36 +426,59 @@ fn view_user_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
] ]
} }
fn view_user<F: Fn(&str) -> String>(l: &User, t: F) -> Node<Msg> { fn view_user<F: Fn(&str) -> String>(l: &User, logged_in_user: &User, t: F) -> Node<Msg> {
let user = UserDelta::from(l.clone()); let user = UserDelta::from(l.clone());
tr![ tr![
{
let user = user.clone();
ev(Ev::Click, |_| {
Msg::Edit(UserEditMsg::EditUserSelected(user))
})
},
match l.role { match l.role {
Role::NotAuthenticated | Role::Disabled => C!("inactive"), Role::NotAuthenticated | Role::Disabled => C!("inactive"),
Role::Regular => C!("regular"), Role::Regular => C!("regular"),
Role::Admin => C!("admin"), Role::Admin => C!("admin"),
}, },
td![&l.id], td![
td![&l.email], {
td![&l.username], let user = user.clone();
match l.role { ev(Ev::Click, |_| {
Role::NotAuthenticated | Role::Disabled | Role::Regular => td![ Msg::Edit(UserEditMsg::EditUserSelected(user))
ev(Ev::Click, |_| Msg::Edit(UserEditMsg::EditUserSelected( })
user },
))), &l.id
t("make-user-admin") ],
], td![
Role::Admin => td![ {
ev(Ev::Click, |_| Msg::Edit(UserEditMsg::EditUserSelected( let user = user.clone();
user ev(Ev::Click, |_| {
))), Msg::Edit(UserEditMsg::EditUserSelected(user))
t("make-user-regular"), })
], },
&l.email
],
td![
{
let user = user.clone();
ev(Ev::Click, |_| {
Msg::Edit(UserEditMsg::EditUserSelected(user))
})
},
&l.username
],
match logged_in_user.role {
Role::Admin => {
match l.role {
Role::NotAuthenticated | Role::Disabled | Role::Regular => td![
ev(Ev::Click, |_| Msg::Edit(UserEditMsg::MakeAdmin(user))),
t("make-user-admin")
],
Role::Admin => td![
ev(Ev::Click, |_| Msg::Edit(UserEditMsg::MakeRegular(user))),
t("make-user-regular"),
],
}
}
Role::Regular => match l.role {
Role::NotAuthenticated | Role::Disabled | Role::Regular => td![t("user")],
Role::Admin => td![t("admin")],
},
Role::NotAuthenticated | Role::Disabled => td![],
} }
] ]
} }

View File

@ -29,6 +29,8 @@ danger-zone-text = Verändern Sie den Code von bereits veröffentlichten Links n
save-edits = Speichere die Veränderungen save-edits = Speichere die Veränderungen
delete-link = Diesen Link löschen delete-link = Diesen Link löschen
user = Benutzer
admin = Administrator
edit-user-headline = Benutzereinstellungen von: {$username} edit-user-headline = Benutzereinstellungen von: {$username}
username = Benutzername username = Benutzername
email = Email email = Email

View File

@ -29,6 +29,8 @@ danger-zone-text = Do not change the code of links that are published. If you do
save-edits = Save edits save-edits = Save edits
delete-link = Delete this link delete-link = Delete this link
user = Benutzer
admin = Administrator
user-headline = User Settings of: {$username} user-headline = User Settings of: {$username}
edit-user-headline = Change Settings of: {$username} edit-user-headline = Change Settings of: {$username}
username = Username username = Username

View File

@ -207,7 +207,7 @@ pub async fn webservice(
) -> Result<actix_web::dev::Server, std::io::Error> { ) -> Result<actix_web::dev::Server, std::io::Error> {
let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port); let host_port = format!("{}:{}", &server_config.internal_ip, &server_config.port);
info!( info!(
"Running on: {}://{}/admin/login/", "Running on: {}://{}/apps/",
&server_config.protocol, host_port &server_config.protocol, host_port
); );
info!( info!(
@ -265,6 +265,7 @@ pub async fn webservice(
"/update_user/", "/update_user/",
web::post().to(views::process_update_user_json), web::post().to(views::process_update_user_json),
) )
.route("/update_privileges/", web::post().to(views::toggle_admin))
.route( .route(
"/get_logged_user/", "/get_logged_user/",
web::post().to(views::get_logged_user_json), web::post().to(views::get_logged_user_json),

View File

@ -255,6 +255,10 @@ pub async fn list_users(
Ok(ListWithOwner { user, list: users }) Ok(ListWithOwner { user, list: users })
} }
RoleGuard::Regular { user } => Ok(ListWithOwner {
user: user.clone(),
list: vec![user],
}),
_ => Err(ServerError::User( _ => Err(ServerError::User(
"Administrator permissions required".to_owned(), "Administrator permissions required".to_owned(),
)), )),
@ -559,14 +563,14 @@ pub async fn update_user(
#[instrument(skip(id))] #[instrument(skip(id))]
pub async fn toggle_admin( pub async fn toggle_admin(
id: &Identity, id: &Identity,
user_id: &str, user_id: Option<i64>,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> Result<Item<User>, ServerError> { ) -> Result<Item<User>, ServerError> {
if let Ok(uid) = user_id.parse::<i64>() { if let Some(uid) = user_id {
let auth = authenticate(id, server_config).await?; let auth = authenticate(id, server_config).await?;
match auth { match auth {
RoleGuard::Admin { .. } => { RoleGuard::Admin { .. } => {
info!("Changing administrator priviledges: "); info!("Changing administrator privileges: ");
let unchanged_user = User::get_user(uid, server_config).await?; let unchanged_user = User::get_user(uid, server_config).await?;

View File

@ -207,15 +207,17 @@ pub async fn process_update_user_json(
#[instrument(skip(id))] #[instrument(skip(id))]
pub async fn toggle_admin( pub async fn toggle_admin(
data: web::Path<String>, user: web::Json<UserDelta>,
config: web::Data<crate::ServerConfig>, config: web::Data<crate::ServerConfig>,
id: Identity, id: Identity,
) -> Result<HttpResponse, ServerError> { ) -> Result<HttpResponse, ServerError> {
let update = queries::toggle_admin(&id, &data.0, &config).await?; let update = queries::toggle_admin(&id, user.id, &config).await?;
Ok(redirect_builder(&format!( Ok(HttpResponse::Ok().json2(&Status::Success(Message {
"/admin/view/profile/{}", message: format!(
update.item.id "Successfully changed privileges or user: {}",
))) update.item.username
),
})))
} }
#[instrument(skip(id))] #[instrument(skip(id))]