) -> Html {
- match (&self.group, &self.common.error) {
+ match (&self.group_and_schema, &self.common.error) {
(None, None) => html! {{"Loading..."}},
(None, Some(e)) => html! {{"Error: "}{e.to_string()}
},
- (Some(u), error) => {
+ (Some((group, schema)), error) => {
html! {
- {self.view_details(u)}
- {self.view_user_list(ctx, u)}
- {self.view_add_user_button(ctx, u)}
+ {self.view_details(ctx, group, schema.clone())}
+ {self.view_user_list(ctx, group)}
+ {self.view_add_user_button(ctx, group)}
{self.view_messages(error)}
}
diff --git a/app/src/components/group_details_form.rs b/app/src/components/group_details_form.rs
new file mode 100644
index 0000000..50b4189
--- /dev/null
+++ b/app/src/components/group_details_form.rs
@@ -0,0 +1,272 @@
+use crate::{
+ components::{
+ form::{
+ attribute_input::{ListAttributeInput, SingleAttributeInput},
+ static_value::StaticValue,
+ submit::Submit,
+ },
+ group_details::{Attribute, AttributeSchema, Group},
+ },
+ infra::{
+ common_component::{CommonComponent, CommonComponentParts},
+ form_utils::{read_all_form_attributes, AttributeValue},
+ schema::AttributeType,
+ },
+};
+use anyhow::{Ok, Result};
+use graphql_client::GraphQLQuery;
+use yew::prelude::*;
+
+/// The GraphQL query sent to the server to update the group details.
+#[derive(GraphQLQuery)]
+#[graphql(
+ schema_path = "../schema.graphql",
+ query_path = "queries/update_group.graphql",
+ response_derives = "Debug",
+ variables_derives = "Clone,PartialEq,Eq",
+ custom_scalars_module = "crate::infra::graphql"
+)]
+pub struct UpdateGroup;
+
+/// A [yew::Component] to display the group details, with a form allowing to edit them.
+pub struct GroupDetailsForm {
+ common: CommonComponentParts,
+ /// True if we just successfully updated the group, to display a success message.
+ just_updated: bool,
+ updated_group_name: bool,
+ group: Group,
+ form_ref: NodeRef,
+}
+
+pub enum Msg {
+ /// A form field changed.
+ Update,
+ /// The "Submit" button was clicked.
+ SubmitClicked,
+ /// We got the response from the server about our update message.
+ GroupUpdated(Result),
+}
+
+#[derive(yew::Properties, Clone, PartialEq)]
+pub struct Props {
+ /// The current group details.
+ pub group: Group,
+ pub group_attributes_schema: Vec,
+ pub is_admin: bool,
+ pub on_display_name_updated: Callback<()>,
+}
+
+impl CommonComponent for GroupDetailsForm {
+ fn handle_msg(
+ &mut self,
+ ctx: &Context,
+ msg: ::Message,
+ ) -> Result {
+ match msg {
+ Msg::Update => Ok(true),
+ Msg::SubmitClicked => self.submit_group_update_form(ctx),
+ Msg::GroupUpdated(Err(e)) => Err(e),
+ Msg::GroupUpdated(Result::Ok(_)) => {
+ self.just_updated = true;
+ if self.updated_group_name {
+ self.updated_group_name = false;
+ ctx.props().on_display_name_updated.emit(());
+ }
+ Ok(true)
+ }
+ }
+ }
+
+ fn mut_common(&mut self) -> &mut CommonComponentParts {
+ &mut self.common
+ }
+}
+
+impl Component for GroupDetailsForm {
+ type Message = Msg;
+ type Properties = Props;
+
+ fn create(ctx: &Context) -> Self {
+ Self {
+ common: CommonComponentParts::::create(),
+ just_updated: false,
+ updated_group_name: false,
+ group: ctx.props().group.clone(),
+ form_ref: NodeRef::default(),
+ }
+ }
+
+ fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool {
+ self.just_updated = false;
+ CommonComponentParts::::update(self, ctx, msg)
+ }
+
+ fn view(&self, ctx: &Context) -> Html {
+ let link = &ctx.link();
+
+ let can_edit =
+ |a: &AttributeSchema| (ctx.props().is_admin || a.is_editable) && !a.is_readonly;
+ let display_field = |a: &AttributeSchema| {
+ if can_edit(a) {
+ get_custom_attribute_input(a, &self.group.attributes)
+ } else {
+ get_custom_attribute_static(a, &self.group.attributes)
+ }
+ };
+ html! {
+
+
+ {
+ if let Some(e) = &self.common.error {
+ html! {
+
+ {e.to_string() }
+
+ }
+ } else { html! {} }
+ }
+
+
{"Group successfully updated!"}
+
+
+ }
+ }
+}
+
+fn get_custom_attribute_input(
+ attribute_schema: &AttributeSchema,
+ group_attributes: &[Attribute],
+) -> Html {
+ let values = group_attributes
+ .iter()
+ .find(|a| a.name == attribute_schema.name)
+ .map(|attribute| attribute.value.clone())
+ .unwrap_or_default();
+ if attribute_schema.is_list {
+ html! {
+ ::into(attribute_schema.attribute_type.clone())}
+ values={values}
+ />
+ }
+ } else {
+ html! {
+ ::into(attribute_schema.attribute_type.clone())}
+ value={values.first().cloned().unwrap_or_default()}
+ />
+ }
+ }
+}
+
+fn get_custom_attribute_static(
+ attribute_schema: &AttributeSchema,
+ group_attributes: &[Attribute],
+) -> Html {
+ let values = group_attributes
+ .iter()
+ .find(|a| a.name == attribute_schema.name)
+ .map(|attribute| attribute.value.clone())
+ .unwrap_or_default();
+ html! {
+
+ {values.into_iter().map(|x| html!{{x}
}).collect::>()}
+
+ }
+}
+
+impl GroupDetailsForm {
+ fn submit_group_update_form(&mut self, ctx: &Context) -> Result {
+ let mut all_values = read_all_form_attributes(
+ ctx.props().group_attributes_schema.iter(),
+ &self.form_ref,
+ ctx.props().is_admin,
+ false,
+ )?;
+ let base_attributes = &self.group.attributes;
+ all_values.retain(|a| {
+ let base_val = base_attributes
+ .iter()
+ .find(|base_val| base_val.name == a.name);
+ base_val
+ .map(|v| v.value != a.values)
+ .unwrap_or(!a.values.is_empty())
+ });
+ if all_values.iter().any(|a| a.name == "display_name") {
+ self.updated_group_name = true;
+ }
+ let remove_attributes: Option> = if all_values.is_empty() {
+ None
+ } else {
+ Some(all_values.iter().map(|a| a.name.clone()).collect())
+ };
+ let insert_attributes: Option> =
+ if remove_attributes.is_none() {
+ None
+ } else {
+ Some(
+ all_values
+ .into_iter()
+ .filter(|a| !a.values.is_empty())
+ .map(
+ |AttributeValue { name, values }| update_group::AttributeValueInput {
+ name,
+ value: values,
+ },
+ )
+ .collect(),
+ )
+ };
+ let mut group_input = update_group::UpdateGroupInput {
+ id: self.group.id,
+ displayName: None,
+ removeAttributes: None,
+ insertAttributes: None,
+ };
+ let default_group_input = group_input.clone();
+ group_input.removeAttributes = remove_attributes;
+ group_input.insertAttributes = insert_attributes;
+ // Nothing changed.
+ if group_input == default_group_input {
+ return Ok(false);
+ }
+ let req = update_group::Variables { group: group_input };
+ self.common.call_graphql::(
+ ctx,
+ req,
+ Msg::GroupUpdated,
+ "Error trying to update group",
+ );
+ Ok(false)
+ }
+}
diff --git a/app/src/components/mod.rs b/app/src/components/mod.rs
index 39897ed..9c09161 100644
--- a/app/src/components/mod.rs
+++ b/app/src/components/mod.rs
@@ -14,6 +14,7 @@ pub mod delete_user;
pub mod delete_user_attribute;
pub mod form;
pub mod group_details;
+pub mod group_details_form;
pub mod group_schema_table;
pub mod group_table;
pub mod login;
diff --git a/server/src/infra/graphql/mutation.rs b/server/src/infra/graphql/mutation.rs
index d7bcaf9..2c2312d 100644
--- a/server/src/infra/graphql/mutation.rs
+++ b/server/src/infra/graphql/mutation.rs
@@ -298,7 +298,14 @@ impl Mutation {
let handler = context
.get_admin_handler()
.ok_or_else(field_error_callback(&span, "Unauthorized group update"))?;
- if group.id == 1 && group.display_name.is_some() {
+ let new_display_name = group.display_name.clone().or_else(|| {
+ group.insert_attributes.as_ref().and_then(|a| {
+ a.iter()
+ .find(|attr| attr.name == "display_name")
+ .map(|attr| attr.value[0].clone())
+ })
+ });
+ if group.id == 1 && new_display_name.is_some() {
span.in_scope(|| debug!("Cannot change lldap_admin group name"));
return Err("Cannot change lldap_admin group name".into());
}
@@ -307,16 +314,18 @@ impl Mutation {
.insert_attributes
.unwrap_or_default()
.into_iter()
+ .filter(|attr| attr.name != "display_name")
.map(|attr| deserialize_attribute(&schema.get_schema().group_attributes, attr, true))
.collect::, _>>()?;
handler
.update_group(UpdateGroupRequest {
group_id: GroupId(group.id),
- display_name: group.display_name.map(Into::into),
+ display_name: new_display_name.map(|s| s.as_str().into()),
delete_attributes: group
.remove_attributes
.unwrap_or_default()
.into_iter()
+ .filter(|attr| attr != "display_name")
.map(Into::into)
.collect(),
insert_attributes,