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_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 v12.57 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                    )
230                })?;
231            Ok(CommandSuccess::GlobalSettings(Box::new(
232                GlobalSettingsSuccess {
233                    settings: document.into(),
234                },
235            )))
236        }
237        SettingsCommand::Update(args) => {
238            let request = if let Some(request) = args.source.read::<UpdateGlobalSettings>().await? {
239                request
240            } else {
241                UpdateGlobalSettings {
242                    disable_registration: args.disable_registration.map(Some),
243                    disable_forgot_password: args.disable_forgot_password.map(Some),
244                    product_name: args.product_name.map(Some),
245                    display_authentication_method: args.display_authentication_method.map(Some),
246                    default_authentication_method: args.default_authentication_method,
247                    spinner_name: args.spinner_name.map(Some),
248                    hide_logo: args.hide_logo.map(Some),
249                    hide_card_counter_list: args.hide_card_counter_list.map(Some),
250                    hide_board_member_list: args.hide_board_member_list.map(Some),
251                    custom_login_logo_image_url: args.custom_login_logo_image_url.map(Some),
252                    custom_login_logo_link_url: args.custom_login_logo_link_url.map(Some),
253                    custom_help_link_url: args.custom_help_link_url.map(Some),
254                    text_below_custom_login_logo: args.text_below_custom_login_logo.map(Some),
255                    automatic_linked_url_schemes: args.automatic_linked_url_schemes.map(Some),
256                    custom_top_left_corner_logo_image_url: args
257                        .custom_top_left_corner_logo_image_url
258                        .map(Some),
259                    custom_top_left_corner_logo_link_url: args
260                        .custom_top_left_corner_logo_link_url
261                        .map(Some),
262                    custom_top_left_corner_logo_height: args
263                        .custom_top_left_corner_logo_height
264                        .map(Some),
265                    oidc_btn_text: args.oidc_btn_text.map(Some),
266                    mail_domain_name: args.mail_domain_name.map(Some),
267                    legal_notice: args.legal_notice.map(Some),
268                    custom_head_enabled: args.custom_head_enabled.map(Some),
269                    custom_head_meta_tags: args.custom_head_meta_tags.map(Some),
270                    custom_head_link_tags: args.custom_head_link_tags.map(Some),
271                    custom_manifest_enabled: args.custom_manifest_enabled.map(Some),
272                    custom_manifest_content: args.custom_manifest_content.map(Some),
273                    custom_asset_links_enabled: args.custom_asset_links_enabled.map(Some),
274                    custom_asset_links_content: args.custom_asset_links_content.map(Some),
275                    accessibility_page_enabled: args.accessibility_page_enabled.map(Some),
276                    accessibility_title: args.accessibility_title.map(Some),
277                    accessibility_content: args.accessibility_content.map(Some),
278                    support_popup_text: args.support_popup_text.map(Some),
279                    support_page_enabled: args.support_page_enabled.map(Some),
280                    support_page_public: args.support_page_public.map(Some),
281                    support_title: args.support_title.map(Some),
282                    support_page_text: args.support_page_text.map(Some),
283                }
284            };
285            let submitted_fields = submitted_fields(&request);
286            input::validate_fields(&submitted_fields)?;
287            let request = PreparedSettingsUpdate::new(&request)
288                .map_err(|error| AppError::invalid_input(error.to_string()))?;
289            let context = AuthenticatedContext::load(factory, store)?;
290            let document = context
291                .client()
292                .update_global_settings(request, context.record().token())
293                .await
294                .map_err(|e| {
295                    map_client_error_with_not_found(
296                        e,
297                        &Redactor::with_secret(context.record().token()),
298                        "admin settings update",
299                        "the Wekan settings document was not found",
300                    )
301                })?;
302            Ok(CommandSuccess::GlobalSettingsUpdated(Box::new(
303                GlobalSettingsUpdateSuccess {
304                    settings: document.into(),
305                    submitted_fields,
306                },
307            )))
308        }
309    }
310}
311
312fn submitted_fields(request: &UpdateGlobalSettings) -> Vec<GlobalSettingField> {
313    let mut fields = Vec::new();
314    if request.disable_registration.is_some() {
315        fields.push(GlobalSettingField::DisableRegistration);
316    }
317    if request.disable_forgot_password.is_some() {
318        fields.push(GlobalSettingField::DisableForgotPassword);
319    }
320    if request.product_name.is_some() {
321        fields.push(GlobalSettingField::ProductName);
322    }
323    if request.display_authentication_method.is_some() {
324        fields.push(GlobalSettingField::DisplayAuthenticationMethod);
325    }
326    if request.default_authentication_method.is_some() {
327        fields.push(GlobalSettingField::DefaultAuthenticationMethod);
328    }
329    if request.spinner_name.is_some() {
330        fields.push(GlobalSettingField::SpinnerName);
331    }
332    if request.hide_logo.is_some() {
333        fields.push(GlobalSettingField::HideLogo);
334    }
335    if request.hide_card_counter_list.is_some() {
336        fields.push(GlobalSettingField::HideCardCounterList);
337    }
338    if request.hide_board_member_list.is_some() {
339        fields.push(GlobalSettingField::HideBoardMemberList);
340    }
341    if request.custom_login_logo_image_url.is_some() {
342        fields.push(GlobalSettingField::CustomLoginLogoImageUrl);
343    }
344    if request.custom_login_logo_link_url.is_some() {
345        fields.push(GlobalSettingField::CustomLoginLogoLinkUrl);
346    }
347    if request.custom_help_link_url.is_some() {
348        fields.push(GlobalSettingField::CustomHelpLinkUrl);
349    }
350    if request.text_below_custom_login_logo.is_some() {
351        fields.push(GlobalSettingField::TextBelowCustomLoginLogo);
352    }
353    if request.automatic_linked_url_schemes.is_some() {
354        fields.push(GlobalSettingField::AutomaticLinkedUrlSchemes);
355    }
356    if request.custom_top_left_corner_logo_image_url.is_some() {
357        fields.push(GlobalSettingField::CustomTopLeftCornerLogoImageUrl);
358    }
359    if request.custom_top_left_corner_logo_link_url.is_some() {
360        fields.push(GlobalSettingField::CustomTopLeftCornerLogoLinkUrl);
361    }
362    if request.custom_top_left_corner_logo_height.is_some() {
363        fields.push(GlobalSettingField::CustomTopLeftCornerLogoHeight);
364    }
365    if request.oidc_btn_text.is_some() {
366        fields.push(GlobalSettingField::OidcBtnText);
367    }
368    if request.mail_domain_name.is_some() {
369        fields.push(GlobalSettingField::MailDomainName);
370    }
371    if request.legal_notice.is_some() {
372        fields.push(GlobalSettingField::LegalNotice);
373    }
374    if request.custom_head_enabled.is_some() {
375        fields.push(GlobalSettingField::CustomHeadEnabled);
376    }
377    if request.custom_head_meta_tags.is_some() {
378        fields.push(GlobalSettingField::CustomHeadMetaTags);
379    }
380    if request.custom_head_link_tags.is_some() {
381        fields.push(GlobalSettingField::CustomHeadLinkTags);
382    }
383    if request.custom_manifest_enabled.is_some() {
384        fields.push(GlobalSettingField::CustomManifestEnabled);
385    }
386    if request.custom_manifest_content.is_some() {
387        fields.push(GlobalSettingField::CustomManifestContent);
388    }
389    if request.custom_asset_links_enabled.is_some() {
390        fields.push(GlobalSettingField::CustomAssetLinksEnabled);
391    }
392    if request.custom_asset_links_content.is_some() {
393        fields.push(GlobalSettingField::CustomAssetLinksContent);
394    }
395    if request.accessibility_page_enabled.is_some() {
396        fields.push(GlobalSettingField::AccessibilityPageEnabled);
397    }
398    if request.accessibility_title.is_some() {
399        fields.push(GlobalSettingField::AccessibilityTitle);
400    }
401    if request.accessibility_content.is_some() {
402        fields.push(GlobalSettingField::AccessibilityContent);
403    }
404    if request.support_popup_text.is_some() {
405        fields.push(GlobalSettingField::SupportPopupText);
406    }
407    if request.support_page_enabled.is_some() {
408        fields.push(GlobalSettingField::SupportPageEnabled);
409    }
410    if request.support_page_public.is_some() {
411        fields.push(GlobalSettingField::SupportPagePublic);
412    }
413    if request.support_title.is_some() {
414        fields.push(GlobalSettingField::SupportTitle);
415    }
416    if request.support_page_text.is_some() {
417        fields.push(GlobalSettingField::SupportPageText);
418    }
419    fields
420}