settingBody.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { TAPi18n } from '/imports/i18n';
  3. import { ALLOWED_WAIT_SPINNERS } from '/config/const';
  4. import LockoutSettings from '/models/lockoutSettings';
  5. BlazeComponent.extendComponent({
  6. onCreated() {
  7. this.error = new ReactiveVar('');
  8. this.loading = new ReactiveVar(false);
  9. this.forgotPasswordSetting = new ReactiveVar(true);
  10. this.generalSetting = new ReactiveVar(true);
  11. this.emailSetting = new ReactiveVar(false);
  12. this.accountSetting = new ReactiveVar(false);
  13. this.tableVisibilityModeSetting = new ReactiveVar(false);
  14. this.announcementSetting = new ReactiveVar(false);
  15. this.accessibilitySetting = new ReactiveVar(false);
  16. this.layoutSetting = new ReactiveVar(false);
  17. this.webhookSetting = new ReactiveVar(false);
  18. this.attachmentSettings = new ReactiveVar(false);
  19. this.cronSettings = new ReactiveVar(false);
  20. Meteor.subscribe('setting');
  21. Meteor.subscribe('mailServer');
  22. Meteor.subscribe('accountSettings');
  23. Meteor.subscribe('tableVisibilityModeSettings');
  24. Meteor.subscribe('announcements');
  25. Meteor.subscribe('accessibilitySettings');
  26. Meteor.subscribe('globalwebhooks');
  27. Meteor.subscribe('lockoutSettings');
  28. },
  29. setError(error) {
  30. this.error.set(error);
  31. },
  32. setLoading(w) {
  33. this.loading.set(w);
  34. },
  35. checkField(selector) {
  36. const value = $(selector).val();
  37. if (!value || value.trim() === '') {
  38. $(selector)
  39. .parents('li.smtp-form')
  40. .addClass('has-error');
  41. throw Error('blank field');
  42. } else {
  43. return value;
  44. }
  45. },
  46. boards() {
  47. const ret = ReactiveCache.getBoards(
  48. {
  49. archived: false,
  50. 'members.userId': Meteor.userId(),
  51. 'members.isAdmin': true,
  52. },
  53. {
  54. sort: { sort: 1 /* boards default sorting */ },
  55. },
  56. );
  57. return ret;
  58. },
  59. toggleForgotPassword() {
  60. this.setLoading(true);
  61. const forgotPasswordClosed = ReactiveCache.getCurrentSetting().disableForgotPassword;
  62. Settings.update(ReactiveCache.getCurrentSetting()._id, {
  63. $set: { disableForgotPassword: !forgotPasswordClosed },
  64. });
  65. this.setLoading(false);
  66. },
  67. toggleRegistration() {
  68. this.setLoading(true);
  69. const registrationClosed = ReactiveCache.getCurrentSetting().disableRegistration;
  70. Settings.update(ReactiveCache.getCurrentSetting()._id, {
  71. $set: { disableRegistration: !registrationClosed },
  72. });
  73. this.setLoading(false);
  74. if (registrationClosed) {
  75. $('.invite-people').slideUp();
  76. } else {
  77. $('.invite-people').slideDown();
  78. }
  79. },
  80. toggleTLS() {
  81. $('#mail-server-tls').toggleClass('is-checked');
  82. },
  83. toggleHideLogo() {
  84. $('#hide-logo').toggleClass('is-checked');
  85. },
  86. toggleHideCardCounterList() {
  87. $('#hide-card-counter-list').toggleClass('is-checked');
  88. },
  89. toggleHideBoardMemberList() {
  90. $('#hide-board-member-list').toggleClass('is-checked');
  91. },
  92. toggleAccessibilityPageEnabled() {
  93. $('#accessibility-page-enabled').toggleClass('is-checked');
  94. },
  95. toggleDisplayAuthenticationMethod() {
  96. $('#display-authentication-method').toggleClass('is-checked');
  97. },
  98. backToMainSettings(event) {
  99. event.preventDefault();
  100. // Reset all settings to false
  101. this.forgotPasswordSetting.set(false);
  102. this.generalSetting.set(true); // Set registration as default
  103. this.emailSetting.set(false);
  104. this.accountSetting.set(false);
  105. this.announcementSetting.set(false);
  106. this.accessibilitySetting.set(false);
  107. this.layoutSetting.set(false);
  108. this.webhookSetting.set(false);
  109. this.attachmentSettings.set(false);
  110. this.cronSettings.set(false);
  111. this.tableVisibilityModeSetting.set(false);
  112. // Update active menu item
  113. $('.side-menu li.active').removeClass('active');
  114. $('.side-menu li:first-child').addClass('active');
  115. },
  116. switchAttachmentMenu(event) {
  117. event.preventDefault();
  118. const target = $(event.target);
  119. const targetID = target.data('id');
  120. // Update active menu item
  121. $('.side-menu li.active').removeClass('active');
  122. target.parent().addClass('active');
  123. // Call the attachment settings component method if available
  124. if (window.attachmentSettings && window.attachmentSettings.switchMenu) {
  125. window.attachmentSettings.switchMenu(event, targetID);
  126. }
  127. },
  128. switchCronMenu(event) {
  129. event.preventDefault();
  130. const target = $(event.target);
  131. const targetID = target.data('id');
  132. // Update active menu item
  133. $('.side-menu li.active').removeClass('active');
  134. target.parent().addClass('active');
  135. // Call the cron settings template method if available
  136. const cronTemplate = Template.instance();
  137. if (cronTemplate && cronTemplate.switchMenu) {
  138. cronTemplate.switchMenu(event, targetID);
  139. }
  140. },
  141. initializeAttachmentSubMenu() {
  142. // Set default sub-menu state for attachment settings
  143. // This will be handled by the attachment settings component
  144. console.log('Initializing attachment sub-menu');
  145. },
  146. initializeCronSubMenu() {
  147. // Set default sub-menu state for cron settings
  148. // This will be handled by the cron settings template
  149. console.log('Initializing cron sub-menu');
  150. },
  151. switchMenu(event) {
  152. const target = $(event.target);
  153. if (!target.hasClass('active')) {
  154. $('.side-menu li.active').removeClass('active');
  155. target.parent().addClass('active');
  156. const targetID = target.data('id');
  157. this.forgotPasswordSetting.set('forgot-password-setting' === targetID);
  158. this.generalSetting.set('registration-setting' === targetID);
  159. this.emailSetting.set('email-setting' === targetID);
  160. this.accountSetting.set('account-setting' === targetID);
  161. this.announcementSetting.set('announcement-setting' === targetID);
  162. this.accessibilitySetting.set('accessibility-setting' === targetID);
  163. this.layoutSetting.set('layout-setting' === targetID);
  164. this.webhookSetting.set('webhook-setting' === targetID);
  165. this.attachmentSettings.set('attachment-settings' === targetID);
  166. this.cronSettings.set('cron-settings' === targetID);
  167. // Initialize sub-menu states
  168. if ('attachment-settings' === targetID) {
  169. this.initializeAttachmentSubMenu();
  170. } else if ('cron-settings' === targetID) {
  171. this.initializeCronSubMenu();
  172. }
  173. this.tableVisibilityModeSetting.set('tableVisibilityMode-setting' === targetID);
  174. }
  175. },
  176. checkBoard(event) {
  177. let target = $(event.target);
  178. if (!target.hasClass('js-toggle-board-choose')) {
  179. target = target.parent();
  180. }
  181. const checkboxId = target.attr('id');
  182. $(`#${checkboxId} .materialCheckBox`).toggleClass('is-checked');
  183. $(`#${checkboxId}`).toggleClass('is-checked');
  184. },
  185. inviteThroughEmail() {
  186. const emails = $('#email-to-invite')
  187. .val()
  188. .toLowerCase()
  189. .trim()
  190. .split('\n')
  191. .join(',')
  192. .split(',');
  193. const boardsToInvite = [];
  194. $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function() {
  195. boardsToInvite.push($(this).data('id'));
  196. });
  197. const validEmails = [];
  198. emails.forEach(email => {
  199. if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
  200. validEmails.push(email.trim());
  201. }
  202. });
  203. if (validEmails.length) {
  204. this.setLoading(true);
  205. Meteor.call('sendInvitation', validEmails, boardsToInvite, () => {
  206. // if (!err) {
  207. // TODO - show more info to user
  208. // }
  209. this.setLoading(false);
  210. });
  211. }
  212. },
  213. saveMailServerInfo() {
  214. this.setLoading(true);
  215. $('li').removeClass('has-error');
  216. try {
  217. const host = this.checkField('#mail-server-host');
  218. const port = this.checkField('#mail-server-port');
  219. const username = $('#mail-server-username')
  220. .val()
  221. .trim();
  222. const password = $('#mail-server-password')
  223. .val()
  224. .trim();
  225. const from = this.checkField('#mail-server-from');
  226. const tls = $('#mail-server-tls.is-checked').length > 0;
  227. Settings.update(ReactiveCache.getCurrentSetting()._id, {
  228. $set: {
  229. 'mailServer.host': host,
  230. 'mailServer.port': port,
  231. 'mailServer.username': username,
  232. 'mailServer.password': password,
  233. 'mailServer.enableTLS': tls,
  234. 'mailServer.from': from,
  235. },
  236. });
  237. } catch (e) {
  238. return;
  239. } finally {
  240. this.setLoading(false);
  241. }
  242. },
  243. saveLayout() {
  244. this.setLoading(true);
  245. $('li').removeClass('has-error');
  246. const productName = ($('#product-name').val() || '').trim();
  247. const customLoginLogoImageUrl = ($('#custom-login-logo-image-url').val() || '').trim();
  248. const customLoginLogoLinkUrl = ($('#custom-login-logo-link-url').val() || '').trim();
  249. const customHelpLinkUrl = ($('#custom-help-link-url').val() || '').trim();
  250. const textBelowCustomLoginLogo = ($('#text-below-custom-login-logo').val() || '').trim();
  251. const automaticLinkedUrlSchemes = ($('#automatic-linked-url-schemes').val() || '').trim();
  252. const customTopLeftCornerLogoImageUrl = ($('#custom-top-left-corner-logo-image-url').val() || '').trim();
  253. const customTopLeftCornerLogoLinkUrl = ($('#custom-top-left-corner-logo-link-url').val() || '').trim();
  254. const customTopLeftCornerLogoHeight = ($('#custom-top-left-corner-logo-height').val() || '').trim();
  255. const oidcBtnText = ($('#oidcBtnTextvalue').val() || '').trim();
  256. const mailDomainName = ($('#mailDomainNamevalue').val() || '').trim();
  257. const legalNotice = ($('#legalNoticevalue').val() || '').trim();
  258. const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true';
  259. const hideCardCounterListChange = $('input[name=hideCardCounterList]:checked').val() === 'true';
  260. const hideBoardMemberListChange = $('input[name=hideBoardMemberList]:checked').val() === 'true';
  261. const displayAuthenticationMethod =
  262. $('input[name=displayAuthenticationMethod]:checked').val() === 'true';
  263. const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
  264. const accessibilityPageEnabled = $('input[name=accessibilityPageEnabled]:checked').val() === 'true';
  265. const accessibilityTitle = ($('#accessibility-title').val() || '').trim();
  266. const accessibilityContent = ($('#accessibility-content').val() || '').trim();
  267. const spinnerName = ($('#spinnerName').val() || '').trim();
  268. try {
  269. Settings.update(ReactiveCache.getCurrentSetting()._id, {
  270. $set: {
  271. productName,
  272. hideLogo: hideLogoChange,
  273. hideCardCounterList: hideCardCounterListChange,
  274. hideBoardMemberList: hideBoardMemberListChange,
  275. customLoginLogoImageUrl,
  276. customLoginLogoLinkUrl,
  277. customHelpLinkUrl,
  278. textBelowCustomLoginLogo,
  279. customTopLeftCornerLogoImageUrl,
  280. customTopLeftCornerLogoLinkUrl,
  281. customTopLeftCornerLogoHeight,
  282. displayAuthenticationMethod,
  283. defaultAuthenticationMethod,
  284. automaticLinkedUrlSchemes,
  285. spinnerName,
  286. oidcBtnText,
  287. mailDomainName,
  288. legalNotice,
  289. accessibilityPageEnabled,
  290. accessibilityTitle,
  291. accessibilityContent,
  292. },
  293. });
  294. } catch (e) {
  295. return;
  296. } finally {
  297. this.setLoading(false);
  298. }
  299. DocHead.setTitle(productName);
  300. },
  301. sendSMTPTestEmail() {
  302. Meteor.call('sendSMTPTestEmail', (err, ret) => {
  303. if (!err && ret) {
  304. const message = `${TAPi18n.__(ret.message)}: ${ret.email}`;
  305. alert(message);
  306. } else {
  307. const reason = err.reason || '';
  308. const message = `${TAPi18n.__(err.error)}\n${reason}`;
  309. alert(message);
  310. }
  311. });
  312. },
  313. events() {
  314. return [
  315. {
  316. 'click a.js-toggle-forgot-password': this.toggleForgotPassword,
  317. 'click a.js-toggle-registration': this.toggleRegistration,
  318. 'click a.js-toggle-tls': this.toggleTLS,
  319. 'click a.js-setting-menu': this.switchMenu,
  320. 'click a.js-toggle-board-choose': this.checkBoard,
  321. 'click button.js-email-invite': this.inviteThroughEmail,
  322. 'click button.js-save': this.saveMailServerInfo,
  323. 'click button.js-send-smtp-test-email': this.sendSMTPTestEmail,
  324. 'click a.js-toggle-hide-logo': this.toggleHideLogo,
  325. 'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList,
  326. 'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList,
  327. 'click button.js-save-layout': this.saveLayout,
  328. 'click a.js-toggle-display-authentication-method': this
  329. .toggleDisplayAuthenticationMethod,
  330. 'click a.js-back-to-main-settings': this.backToMainSettings,
  331. 'click a.js-attachment-storage-settings': this.switchAttachmentMenu,
  332. 'click a.js-attachment-migration': this.switchAttachmentMenu,
  333. 'click a.js-attachment-monitoring': this.switchAttachmentMenu,
  334. 'click a.js-cron-migrations': this.switchCronMenu,
  335. 'click a.js-cron-board-operations': this.switchCronMenu,
  336. 'click a.js-cron-jobs': this.switchCronMenu,
  337. 'click a.js-cron-add': this.switchCronMenu,
  338. },
  339. ];
  340. },
  341. }).register('setting');
  342. BlazeComponent.extendComponent({
  343. saveAccountsChange() {
  344. const allowEmailChange =
  345. $('input[name=allowEmailChange]:checked').val() === 'true';
  346. const allowUserNameChange =
  347. $('input[name=allowUserNameChange]:checked').val() === 'true';
  348. const allowUserDelete =
  349. $('input[name=allowUserDelete]:checked').val() === 'true';
  350. AccountSettings.update('accounts-allowEmailChange', {
  351. $set: { booleanValue: allowEmailChange },
  352. });
  353. AccountSettings.update('accounts-allowUserNameChange', {
  354. $set: { booleanValue: allowUserNameChange },
  355. });
  356. AccountSettings.update('accounts-allowUserDelete', {
  357. $set: { booleanValue: allowUserDelete },
  358. });
  359. },
  360. // Brute force lockout settings method moved to lockedUsersBody.js
  361. allowEmailChange() {
  362. return AccountSettings.findOne('accounts-allowEmailChange')?.booleanValue || false;
  363. },
  364. allowUserNameChange() {
  365. return AccountSettings.findOne('accounts-allowUserNameChange')?.booleanValue || false;
  366. },
  367. allowUserDelete() {
  368. return AccountSettings.findOne('accounts-allowUserDelete')?.booleanValue || false;
  369. },
  370. // Lockout settings helper methods moved to lockedUsersBody.js
  371. allBoardsHideActivities() {
  372. Meteor.call('setAllBoardsHideActivities', (err, ret) => {
  373. if (!err && ret) {
  374. if (ret === true) {
  375. const message = `${TAPi18n.__(
  376. 'now-activities-of-all-boards-are-hidden',
  377. )}`;
  378. alert(message);
  379. }
  380. } else {
  381. const reason = err.reason || '';
  382. const message = `${TAPi18n.__(err.error)}\n${reason}`;
  383. alert(message);
  384. }
  385. });
  386. },
  387. events() {
  388. return [
  389. {
  390. 'click button.js-accounts-save': this.saveAccountsChange,
  391. },
  392. {
  393. 'click button.js-all-boards-hide-activities': this.allBoardsHideActivities,
  394. },
  395. ];
  396. },
  397. }).register('accountSettings');
  398. BlazeComponent.extendComponent({
  399. saveTableVisibilityChange() {
  400. const allowPrivateOnly =
  401. $('input[name=allowPrivateOnly]:checked').val() === 'true';
  402. TableVisibilityModeSettings.update('tableVisibilityMode-allowPrivateOnly', {
  403. $set: { booleanValue: allowPrivateOnly },
  404. });
  405. },
  406. allowPrivateOnly() {
  407. return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
  408. },
  409. allBoardsHideActivities() {
  410. Meteor.call('setAllBoardsHideActivities', (err, ret) => {
  411. if (!err && ret) {
  412. if (ret === true) {
  413. const message = `${TAPi18n.__(
  414. 'now-activities-of-all-boards-are-hidden',
  415. )}`;
  416. alert(message);
  417. }
  418. } else {
  419. const reason = err.reason || '';
  420. const message = `${TAPi18n.__(err.error)}\n${reason}`;
  421. alert(message);
  422. }
  423. });
  424. },
  425. events() {
  426. return [
  427. {
  428. 'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange,
  429. },
  430. {
  431. 'click button.js-all-boards-hide-activities': this.allBoardsHideActivities,
  432. },
  433. ];
  434. },
  435. }).register('tableVisibilityModeSettings');
  436. BlazeComponent.extendComponent({
  437. onCreated() {
  438. this.loading = new ReactiveVar(false);
  439. },
  440. setLoading(w) {
  441. this.loading.set(w);
  442. },
  443. currentAnnouncements() {
  444. return Announcements.findOne();
  445. },
  446. saveMessage() {
  447. const message = $('#admin-announcement')
  448. .val()
  449. .trim();
  450. Announcements.update(Announcements.findOne()._id, {
  451. $set: { body: message },
  452. });
  453. },
  454. toggleActive() {
  455. this.setLoading(true);
  456. const announcements = this.currentAnnouncements();
  457. const isActive = announcements.enabled;
  458. Announcements.update(announcements._id, {
  459. $set: { enabled: !isActive },
  460. });
  461. this.setLoading(false);
  462. if (isActive) {
  463. $('.admin-announcement').slideUp();
  464. } else {
  465. $('.admin-announcement').slideDown();
  466. }
  467. },
  468. events() {
  469. return [
  470. {
  471. 'click a.js-toggle-activemessage': this.toggleActive,
  472. 'click button.js-announcement-save': this.saveMessage,
  473. },
  474. ];
  475. },
  476. }).register('announcementSettings');
  477. BlazeComponent.extendComponent({
  478. onCreated() {
  479. this.loading = new ReactiveVar(false);
  480. },
  481. setLoading(w) {
  482. this.loading.set(w);
  483. },
  484. currentAccessibility() {
  485. return AccessibilitySettings.findOne();
  486. },
  487. saveAccessibility() {
  488. const title = $('#admin-accessibility-title')
  489. .val()
  490. .trim();
  491. const content = $('#admin-accessibility-content')
  492. .val()
  493. .trim();
  494. AccessibilitySettings.update(AccessibilitySettings.findOne()._id, {
  495. $set: {
  496. title: title,
  497. body: content
  498. },
  499. });
  500. },
  501. toggleAccessibility() {
  502. this.setLoading(true);
  503. const accessibilitySetting = this.currentAccessibility();
  504. const isActive = accessibilitySetting.enabled;
  505. AccessibilitySettings.update(accessibilitySetting._id, {
  506. $set: { enabled: !isActive },
  507. });
  508. this.setLoading(false);
  509. if (isActive) {
  510. $('.accessibility-content').slideUp();
  511. } else {
  512. $('.accessibility-content').slideDown();
  513. }
  514. },
  515. events() {
  516. return [
  517. {
  518. 'click a.js-toggle-accessibility': this.toggleAccessibility,
  519. 'click button.js-accessibility-save': this.saveAccessibility,
  520. },
  521. ];
  522. },
  523. }).register('accessibilitySettings');
  524. Template.selectAuthenticationMethod.onCreated(function() {
  525. this.authenticationMethods = new ReactiveVar([]);
  526. Meteor.call('getAuthenticationsEnabled', (_, result) => {
  527. if (result) {
  528. // TODO : add a management of different languages
  529. // (ex {value: ldap, text: TAPi18n.__('ldap', {}, T9n.getLanguage() || 'en')})
  530. this.authenticationMethods.set([
  531. { value: 'password' },
  532. // Gets only the authentication methods availables
  533. ...Object.entries(result)
  534. .filter(e => e[1])
  535. .map(e => ({ value: e[0] })),
  536. ]);
  537. }
  538. });
  539. });
  540. Template.selectAuthenticationMethod.helpers({
  541. authentications() {
  542. return Template.instance().authenticationMethods.get();
  543. },
  544. isSelected(match) {
  545. return Template.instance().data.authenticationMethod === match;
  546. },
  547. });
  548. Template.selectSpinnerName.helpers({
  549. spinners() {
  550. return ALLOWED_WAIT_SPINNERS;
  551. },
  552. isSelected(match) {
  553. return Template.instance().data.spinnerName === match;
  554. },
  555. });