settingBody.js 16 KB

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