settingBody.js 18 KB

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