Skip to main content

wekan_cli/commands/admin/
settings.rs

1use super::input;
2use crate::client::admin::{PreparedSettingsUpdate, settings::UpdateGlobalSettings};
3use crate::{
4    client::WekanClientFactory,
5    command_result::*,
6    commands::{
7        authenticated::AuthenticatedContext,
8        client_error::{map_client_error, map_partial_mutation_client_error_with_not_found},
9    },
10    credentials::CredentialStore,
11    error::AppError,
12    redaction::Redactor,
13};
14use clap::{ArgGroup, Args, Subcommand};
15#[derive(Debug, Args)]
16pub struct SettingsArgs {
17    #[command(subcommand)]
18    pub command: SettingsCommand,
19}
20#[derive(Debug, Subcommand)]
21pub enum SettingsCommand {
22    /// Return the complete mapped settings document.
23    Get,
24    /// Apply native settings updates, with no read-before-write merging.
25    Update(Box<UpdateArgs>),
26}
27#[derive(Debug, Args)]
28#[command(group(ArgGroup::new("setting_flags").args([
29    "disable_registration",
30    "disable_forgot_password",
31    "product_name",
32    "display_authentication_method",
33    "default_authentication_method",
34    "spinner_name",
35    "hide_logo",
36    "hide_card_counter_list",
37    "hide_board_member_list",
38    "custom_login_logo_image_url",
39    "custom_login_logo_link_url",
40    "custom_help_link_url",
41    "text_below_custom_login_logo",
42    "automatic_linked_url_schemes",
43    "custom_top_left_corner_logo_image_url",
44    "custom_top_left_corner_logo_link_url",
45    "custom_top_left_corner_logo_height",
46    "oidc_btn_text",
47    "mail_domain_name",
48    "legal_notice",
49    "custom_head_enabled",
50    "custom_head_meta_tags",
51    "custom_head_link_tags",
52    "custom_manifest_enabled",
53    "custom_manifest_content",
54    "custom_asset_links_enabled",
55    "custom_asset_links_content",
56    "accessibility_page_enabled",
57    "accessibility_title",
58    "accessibility_content",
59    "support_popup_text",
60    "support_page_enabled",
61    "support_page_public",
62    "support_title",
63    "support_page_text",
64]).multiple(true).conflicts_with("settings_source")), group(ArgGroup::new("update_input").args([
65    "settings_file",
66    "settings_stdin",
67    "disable_registration",
68    "disable_forgot_password",
69    "product_name",
70    "display_authentication_method",
71    "default_authentication_method",
72    "spinner_name",
73    "hide_logo",
74    "hide_card_counter_list",
75    "hide_board_member_list",
76    "custom_login_logo_image_url",
77    "custom_login_logo_link_url",
78    "custom_help_link_url",
79    "text_below_custom_login_logo",
80    "automatic_linked_url_schemes",
81    "custom_top_left_corner_logo_image_url",
82    "custom_top_left_corner_logo_link_url",
83    "custom_top_left_corner_logo_height",
84    "oidc_btn_text",
85    "mail_domain_name",
86    "legal_notice",
87    "custom_head_enabled",
88    "custom_head_meta_tags",
89    "custom_head_link_tags",
90    "custom_manifest_enabled",
91    "custom_manifest_content",
92    "custom_asset_links_enabled",
93    "custom_asset_links_content",
94    "accessibility_page_enabled",
95    "accessibility_title",
96    "accessibility_content",
97    "support_popup_text",
98    "support_page_enabled",
99    "support_page_public",
100    "support_title",
101    "support_page_text",
102]).required(true).multiple(true)), after_help = "Only the v11.06 REST allowlist is supported; SMTP/mailServer is excluded. Omitted fields remain unchanged. JSON source options conflict with all setting flags.")]
103pub struct UpdateArgs {
104    #[command(flatten)]
105    source: input::SourceArgs,
106    /// Set disable registration. Explicit true or false.
107    #[arg(long = "disable-registration")]
108    disable_registration: Option<bool>,
109    /// Set disable forgot password. Explicit true or false.
110    #[arg(long = "disable-forgot-password")]
111    disable_forgot_password: Option<bool>,
112    /// Set product name.
113    #[arg(long = "product-name")]
114    product_name: Option<String>,
115    /// Set display authentication method. Explicit true or false.
116    #[arg(long = "display-authentication-method")]
117    display_authentication_method: Option<bool>,
118    /// Set default authentication method.
119    #[arg(long = "default-authentication-method")]
120    default_authentication_method: Option<String>,
121    /// Set spinner name.
122    #[arg(long = "spinner-name")]
123    spinner_name: Option<String>,
124    /// Set hide logo. Explicit true or false.
125    #[arg(long = "hide-logo")]
126    hide_logo: Option<bool>,
127    /// Set hide card counter list. Explicit true or false.
128    #[arg(long = "hide-card-counter-list")]
129    hide_card_counter_list: Option<bool>,
130    /// Set hide board member list. Explicit true or false.
131    #[arg(long = "hide-board-member-list")]
132    hide_board_member_list: Option<bool>,
133    /// Set custom login logo image url.
134    #[arg(long = "custom-login-logo-image-url")]
135    custom_login_logo_image_url: Option<String>,
136    /// Set custom login logo link url.
137    #[arg(long = "custom-login-logo-link-url")]
138    custom_login_logo_link_url: Option<String>,
139    /// Set custom help link url.
140    #[arg(long = "custom-help-link-url")]
141    custom_help_link_url: Option<String>,
142    /// Set text below custom login logo.
143    #[arg(long = "text-below-custom-login-logo")]
144    text_below_custom_login_logo: Option<String>,
145    /// Set automatic linked url schemes.
146    #[arg(long = "automatic-linked-url-schemes")]
147    automatic_linked_url_schemes: Option<String>,
148    /// Set custom top left corner logo image url.
149    #[arg(long = "custom-top-left-corner-logo-image-url")]
150    custom_top_left_corner_logo_image_url: Option<String>,
151    /// Set custom top left corner logo link url.
152    #[arg(long = "custom-top-left-corner-logo-link-url")]
153    custom_top_left_corner_logo_link_url: Option<String>,
154    /// Set custom top left corner logo height.
155    #[arg(long = "custom-top-left-corner-logo-height")]
156    custom_top_left_corner_logo_height: Option<String>,
157    /// Set oidc btn text.
158    #[arg(long = "oidc-btn-text")]
159    oidc_btn_text: Option<String>,
160    /// Set mail domain name.
161    #[arg(long = "mail-domain-name")]
162    mail_domain_name: Option<String>,
163    /// Set legal notice.
164    #[arg(long = "legal-notice")]
165    legal_notice: Option<String>,
166    /// Set custom head enabled. Explicit true or false.
167    #[arg(long = "custom-head-enabled")]
168    custom_head_enabled: Option<bool>,
169    /// Set custom head meta tags.
170    #[arg(long = "custom-head-meta-tags")]
171    custom_head_meta_tags: Option<String>,
172    /// Set custom head link tags.
173    #[arg(long = "custom-head-link-tags")]
174    custom_head_link_tags: Option<String>,
175    /// Set custom manifest enabled. Explicit true or false.
176    #[arg(long = "custom-manifest-enabled")]
177    custom_manifest_enabled: Option<bool>,
178    /// Set custom manifest content.
179    #[arg(long = "custom-manifest-content")]
180    custom_manifest_content: Option<String>,
181    /// Set custom asset links enabled. Explicit true or false.
182    #[arg(long = "custom-asset-links-enabled")]
183    custom_asset_links_enabled: Option<bool>,
184    /// Set custom asset links content.
185    #[arg(long = "custom-asset-links-content")]
186    custom_asset_links_content: Option<String>,
187    /// Set accessibility page enabled. Explicit true or false.
188    #[arg(long = "accessibility-page-enabled")]
189    accessibility_page_enabled: Option<bool>,
190    /// Set accessibility title.
191    #[arg(long = "accessibility-title")]
192    accessibility_title: Option<String>,
193    /// Set accessibility content.
194    #[arg(long = "accessibility-content")]
195    accessibility_content: Option<String>,
196    /// Set support popup text.
197    #[arg(long = "support-popup-text")]
198    support_popup_text: Option<String>,
199    /// Set support page enabled. Explicit true or false.
200    #[arg(long = "support-page-enabled")]
201    support_page_enabled: Option<bool>,
202    /// Set support page public. Explicit true or false.
203    #[arg(long = "support-page-public")]
204    support_page_public: Option<bool>,
205    /// Set support title.
206    #[arg(long = "support-title")]
207    support_title: Option<String>,
208    /// Set support page text.
209    #[arg(long = "support-page-text")]
210    support_page_text: Option<String>,
211}
212pub(crate) async fn execute(
213    command: SettingsCommand,
214    factory: &WekanClientFactory,
215    store: &dyn CredentialStore,
216) -> Result<CommandSuccess, AppError> {
217    match command {
218        SettingsCommand::Get => {
219            let context = AuthenticatedContext::load(factory, store)?;
220            let document = context
221                .client()
222                .global_settings(context.record().token())
223                .await
224                .map_err(|e| {
225                    map_client_error(
226                        e,
227                        &Redactor::with_secret(context.record().token()),
228                        "admin settings get",
229                        false,
230                    )
231                })?;
232            Ok(CommandSuccess::GlobalSettings(Box::new(
233                GlobalSettingsSuccess {
234                    settings: document.into(),
235                },
236            )))
237        }
238        SettingsCommand::Update(args) => {
239            let request = if let Some(request) = args.source.read::<UpdateGlobalSettings>().await? {
240                request
241            } else {
242                UpdateGlobalSettings {
243                    disable_registration: args.disable_registration,
244                    disable_forgot_password: args.disable_forgot_password,
245                    product_name: args.product_name,
246                    display_authentication_method: args.display_authentication_method,
247                    default_authentication_method: args.default_authentication_method,
248                    spinner_name: args.spinner_name,
249                    hide_logo: args.hide_logo,
250                    hide_card_counter_list: args.hide_card_counter_list,
251                    hide_board_member_list: args.hide_board_member_list,
252                    custom_login_logo_image_url: args.custom_login_logo_image_url,
253                    custom_login_logo_link_url: args.custom_login_logo_link_url,
254                    custom_help_link_url: args.custom_help_link_url,
255                    text_below_custom_login_logo: args.text_below_custom_login_logo,
256                    automatic_linked_url_schemes: args.automatic_linked_url_schemes,
257                    custom_top_left_corner_logo_image_url: args
258                        .custom_top_left_corner_logo_image_url,
259                    custom_top_left_corner_logo_link_url: args.custom_top_left_corner_logo_link_url,
260                    custom_top_left_corner_logo_height: args.custom_top_left_corner_logo_height,
261                    oidc_btn_text: args.oidc_btn_text,
262                    mail_domain_name: args.mail_domain_name,
263                    legal_notice: args.legal_notice,
264                    custom_head_enabled: args.custom_head_enabled,
265                    custom_head_meta_tags: args.custom_head_meta_tags,
266                    custom_head_link_tags: args.custom_head_link_tags,
267                    custom_manifest_enabled: args.custom_manifest_enabled,
268                    custom_manifest_content: args.custom_manifest_content,
269                    custom_asset_links_enabled: args.custom_asset_links_enabled,
270                    custom_asset_links_content: args.custom_asset_links_content,
271                    accessibility_page_enabled: args.accessibility_page_enabled,
272                    accessibility_title: args.accessibility_title,
273                    accessibility_content: args.accessibility_content,
274                    support_popup_text: args.support_popup_text,
275                    support_page_enabled: args.support_page_enabled,
276                    support_page_public: args.support_page_public,
277                    support_title: args.support_title,
278                    support_page_text: args.support_page_text,
279                }
280            };
281            let submitted_fields = submitted_fields(&request);
282            input::validate_fields(&submitted_fields)?;
283            let request = PreparedSettingsUpdate::new(&request)
284                .map_err(|error| AppError::invalid_input(error.to_string()))?;
285            let context = AuthenticatedContext::load(factory, store)?;
286            let document = context
287                .client()
288                .update_global_settings(request, context.record().token())
289                .await
290                .map_err(|e| {
291                    map_partial_mutation_client_error_with_not_found(
292                        e,
293                        &Redactor::with_secret(context.record().token()),
294                        "admin settings update",
295                        "the Wekan settings document was not found",
296                    )
297                })?;
298            Ok(CommandSuccess::GlobalSettingsUpdated(Box::new(
299                GlobalSettingsUpdateSuccess {
300                    settings: document.into(),
301                    submitted_fields,
302                },
303            )))
304        }
305    }
306}
307
308fn submitted_fields(request: &UpdateGlobalSettings) -> Vec<GlobalSettingField> {
309    let mut fields = Vec::new();
310    if request.disable_registration.is_some() {
311        fields.push(GlobalSettingField::DisableRegistration);
312    }
313    if request.disable_forgot_password.is_some() {
314        fields.push(GlobalSettingField::DisableForgotPassword);
315    }
316    if request.product_name.is_some() {
317        fields.push(GlobalSettingField::ProductName);
318    }
319    if request.display_authentication_method.is_some() {
320        fields.push(GlobalSettingField::DisplayAuthenticationMethod);
321    }
322    if request.default_authentication_method.is_some() {
323        fields.push(GlobalSettingField::DefaultAuthenticationMethod);
324    }
325    if request.spinner_name.is_some() {
326        fields.push(GlobalSettingField::SpinnerName);
327    }
328    if request.hide_logo.is_some() {
329        fields.push(GlobalSettingField::HideLogo);
330    }
331    if request.hide_card_counter_list.is_some() {
332        fields.push(GlobalSettingField::HideCardCounterList);
333    }
334    if request.hide_board_member_list.is_some() {
335        fields.push(GlobalSettingField::HideBoardMemberList);
336    }
337    if request.custom_login_logo_image_url.is_some() {
338        fields.push(GlobalSettingField::CustomLoginLogoImageUrl);
339    }
340    if request.custom_login_logo_link_url.is_some() {
341        fields.push(GlobalSettingField::CustomLoginLogoLinkUrl);
342    }
343    if request.custom_help_link_url.is_some() {
344        fields.push(GlobalSettingField::CustomHelpLinkUrl);
345    }
346    if request.text_below_custom_login_logo.is_some() {
347        fields.push(GlobalSettingField::TextBelowCustomLoginLogo);
348    }
349    if request.automatic_linked_url_schemes.is_some() {
350        fields.push(GlobalSettingField::AutomaticLinkedUrlSchemes);
351    }
352    if request.custom_top_left_corner_logo_image_url.is_some() {
353        fields.push(GlobalSettingField::CustomTopLeftCornerLogoImageUrl);
354    }
355    if request.custom_top_left_corner_logo_link_url.is_some() {
356        fields.push(GlobalSettingField::CustomTopLeftCornerLogoLinkUrl);
357    }
358    if request.custom_top_left_corner_logo_height.is_some() {
359        fields.push(GlobalSettingField::CustomTopLeftCornerLogoHeight);
360    }
361    if request.oidc_btn_text.is_some() {
362        fields.push(GlobalSettingField::OidcBtnText);
363    }
364    if request.mail_domain_name.is_some() {
365        fields.push(GlobalSettingField::MailDomainName);
366    }
367    if request.legal_notice.is_some() {
368        fields.push(GlobalSettingField::LegalNotice);
369    }
370    if request.custom_head_enabled.is_some() {
371        fields.push(GlobalSettingField::CustomHeadEnabled);
372    }
373    if request.custom_head_meta_tags.is_some() {
374        fields.push(GlobalSettingField::CustomHeadMetaTags);
375    }
376    if request.custom_head_link_tags.is_some() {
377        fields.push(GlobalSettingField::CustomHeadLinkTags);
378    }
379    if request.custom_manifest_enabled.is_some() {
380        fields.push(GlobalSettingField::CustomManifestEnabled);
381    }
382    if request.custom_manifest_content.is_some() {
383        fields.push(GlobalSettingField::CustomManifestContent);
384    }
385    if request.custom_asset_links_enabled.is_some() {
386        fields.push(GlobalSettingField::CustomAssetLinksEnabled);
387    }
388    if request.custom_asset_links_content.is_some() {
389        fields.push(GlobalSettingField::CustomAssetLinksContent);
390    }
391    if request.accessibility_page_enabled.is_some() {
392        fields.push(GlobalSettingField::AccessibilityPageEnabled);
393    }
394    if request.accessibility_title.is_some() {
395        fields.push(GlobalSettingField::AccessibilityTitle);
396    }
397    if request.accessibility_content.is_some() {
398        fields.push(GlobalSettingField::AccessibilityContent);
399    }
400    if request.support_popup_text.is_some() {
401        fields.push(GlobalSettingField::SupportPopupText);
402    }
403    if request.support_page_enabled.is_some() {
404        fields.push(GlobalSettingField::SupportPageEnabled);
405    }
406    if request.support_page_public.is_some() {
407        fields.push(GlobalSettingField::SupportPagePublic);
408    }
409    if request.support_title.is_some() {
410        fields.push(GlobalSettingField::SupportTitle);
411    }
412    if request.support_page_text.is_some() {
413        fields.push(GlobalSettingField::SupportPageText);
414    }
415    fields
416}