Settings.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. <template>
  2. <section class="settings">
  3. <b-loading :is-full-page="true" v-if="isLoading" active />
  4. <header class="columns">
  5. <div class="column is-half">
  6. <h1 class="title is-4">{{ $t('settings.title') }}</h1>
  7. </div>
  8. <div class="column has-text-right">
  9. <b-button :disabled="!hasFormChanged"
  10. type="is-primary" icon-left="content-save-outline"
  11. @click="onSubmit" class="isSaveEnabled">{{ $t('globals.buttons.save') }}</b-button>
  12. </div>
  13. </header>
  14. <hr />
  15. <section class="wrap-small">
  16. <form @submit.prevent="onSubmit">
  17. <b-tabs type="is-boxed" :animated="false">
  18. <b-tab-item :label="$t('settings.general.name')" label-position="on-border">
  19. <div class="items">
  20. <b-field :label="$t('settings.general.rootURL')" label-position="on-border"
  21. :message="$t('settings.general.rootURLHelp')">
  22. <b-input v-model="form['app.root_url']" name="app.root_url"
  23. placeholder='https://listmonk.yoursite.com' :maxlength="300" />
  24. </b-field>
  25. <b-field :label="$t('settings.general.logoURL')" label-position="on-border"
  26. :message="$t('settings.general.logoURLHelp')">
  27. <b-input v-model="form['app.logo_url']" name="app.logo_url"
  28. placeholder='https://listmonk.yoursite.com/logo.png' :maxlength="300" />
  29. </b-field>
  30. <b-field :label="$t('settings.general.faviconURL')" label-position="on-border"
  31. :message="$t('settings.general.faviconURLHelp')">
  32. <b-input v-model="form['app.favicon_url']" name="app.favicon_url"
  33. placeholder='https://listmonk.yoursite.com/favicon.png' :maxlength="300" />
  34. </b-field>
  35. <hr />
  36. <b-field :label="$t('settings.general.fromEmail')" label-position="on-border"
  37. :message="$t('settings.general.fromEmailHelp')">
  38. <b-input v-model="form['app.from_email']" name="app.from_email"
  39. placeholder='Listmonk <noreply@listmonk.yoursite.com>'
  40. pattern="(.+?)\s<(.+?)@(.+?)>" :maxlength="300" />
  41. </b-field>
  42. <b-field :label="$t('settings.general.adminNotifEmails')" label-position="on-border"
  43. :message="$t('settings.general.adminNotifEmailsHelp')">
  44. <b-taginput v-model="form['app.notify_emails']" name="app.notify_emails"
  45. :before-adding="(v) => v.match(/(.+?)@(.+?)/)"
  46. placeholder='you@yoursite.com' />
  47. </b-field>
  48. <b-field :label="$t('settings.general.enablePublicSubPage')"
  49. :message="$t('settings.general.enablePublicSubPageHelp')">
  50. <b-switch v-model="form['app.enable_public_subscription_page']"
  51. name="app.enable_public_subscription_page" />
  52. </b-field>
  53. <hr />
  54. <b-field :label="$t('settings.general.language')" label-position="on-border">
  55. <b-select v-model="form['app.lang']" name="app.lang">
  56. <option v-for="l in serverConfig.langs" :key="l.code" :value="l.code">
  57. {{ l.name }}
  58. </option>
  59. </b-select>
  60. </b-field>
  61. </div>
  62. </b-tab-item><!-- general -->
  63. <b-tab-item :label="$t('settings.performance.name')">
  64. <div class="items">
  65. <b-field :label="$t('settings.performance.concurrency')" label-position="on-border"
  66. :message="$t('settings.performance.concurrencyHelp')">
  67. <b-numberinput v-model="form['app.concurrency']"
  68. name="app.concurrency" type="is-light"
  69. placeholder="5" min="1" max="10000" />
  70. </b-field>
  71. <b-field :label="$t('settings.performance.messageRate')" label-position="on-border"
  72. :message="$t('settings.performance.messageRateHelp')">
  73. <b-numberinput v-model="form['app.message_rate']"
  74. name="app.message_rate" type="is-light"
  75. placeholder="5" min="1" max="100000" />
  76. </b-field>
  77. <b-field :label="$t('settings.performance.batchSize')" label-position="on-border"
  78. :message="$t('settings.performance.batchSizeHelp')">
  79. <b-numberinput v-model="form['app.batch_size']"
  80. name="app.batch_size" type="is-light"
  81. placeholder="1000" min="1" max="100000" />
  82. </b-field>
  83. <b-field :label="$t('settings.performance.maxErrThreshold')"
  84. label-position="on-border"
  85. :message="$t('settings.performance.maxErrThresholdHelp')">
  86. <b-numberinput v-model="form['app.max_send_errors']"
  87. name="app.max_send_errors" type="is-light"
  88. placeholder="1999" min="0" max="100000" />
  89. </b-field>
  90. <div>
  91. <div class="columns">
  92. <div class="column is-6">
  93. <b-field :label="$t('settings.performance.slidingWindow')"
  94. :message="$t('settings.performance.slidingWindowHelp')">
  95. <b-switch v-model="form['app.message_sliding_window']"
  96. name="app.message_sliding_window" />
  97. </b-field>
  98. </div>
  99. <div class="column is-3"
  100. :class="{'disabled': !form['app.message_sliding_window']}">
  101. <b-field :label="$t('settings.performance.slidingWindowRate')"
  102. label-position="on-border"
  103. :message="$t('settings.performance.slidingWindowRateHelp')">
  104. <b-numberinput v-model="form['app.message_sliding_window_rate']"
  105. name="sliding_window_rate" type="is-light"
  106. controls-position="compact"
  107. :disabled="!form['app.message_sliding_window']"
  108. placeholder="25" min="1" max="10000000" />
  109. </b-field>
  110. </div>
  111. <div class="column is-3"
  112. :class="{'disabled': !form['app.message_sliding_window']}">
  113. <b-field :label="$t('settings.performance.slidingWindowDuration')"
  114. label-position="on-border"
  115. :message="$t('settings.performance.slidingWindowDurationHelp')">
  116. <b-input v-model="form['app.message_sliding_window_duration']"
  117. name="sliding_window_duration"
  118. :disabled="!form['app.message_sliding_window']"
  119. placeholder="1h" :pattern="regDuration" :maxlength="10" />
  120. </b-field>
  121. </div>
  122. </div>
  123. </div><!-- sliding window -->
  124. </div>
  125. </b-tab-item><!-- performance -->
  126. <b-tab-item :label="$t('settings.privacy.name')">
  127. <div class="items">
  128. <b-field :label="$t('settings.privacy.individualSubTracking')"
  129. :message="$t('settings.privacy.individualSubTrackingHelp')">
  130. <b-switch v-model="form['privacy.individual_tracking']"
  131. name="privacy.individual_tracking" />
  132. </b-field>
  133. <b-field :label="$t('settings.privacy.listUnsubHeader')"
  134. :message="$t('settings.privacy.listUnsubHeaderHelp')">
  135. <b-switch v-model="form['privacy.unsubscribe_header']"
  136. name="privacy.unsubscribe_header" />
  137. </b-field>
  138. <b-field :label="$t('settings.privacy.allowBlocklist')"
  139. :message="$t('settings.privacy.allowBlocklistHelp')">
  140. <b-switch v-model="form['privacy.allow_blocklist']"
  141. name="privacy.allow_blocklist" />
  142. </b-field>
  143. <b-field :label="$t('settings.privacy.allowExport')"
  144. :message="$t('settings.privacy.allowExportHelp')">
  145. <b-switch v-model="form['privacy.allow_export']"
  146. name="privacy.allow_export" />
  147. </b-field>
  148. <b-field :label="$t('settings.privacy.allowWipe')"
  149. :message="$t('settings.privacy.allowWipeHelp')">
  150. <b-switch v-model="form['privacy.allow_wipe']"
  151. name="privacy.allow_wipe" />
  152. </b-field>
  153. </div>
  154. </b-tab-item><!-- privacy -->
  155. <b-tab-item :label="$t('settings.media.title')">
  156. <div class="items">
  157. <b-field :label="$t('settings.media.provider')" label-position="on-border">
  158. <b-select v-model="form['upload.provider']" name="upload.provider">
  159. <option value="filesystem">filesystem</option>
  160. <option value="s3">s3</option>
  161. </b-select>
  162. </b-field>
  163. <div class="block" v-if="form['upload.provider'] === 'filesystem'">
  164. <b-field :label="$t('settings.media.upload.path')" label-position="on-border"
  165. :message="$t('settings.media.upload.pathHelp')">
  166. <b-input v-model="form['upload.filesystem.upload_path']"
  167. name="app.upload_path" placeholder='/home/listmonk/uploads'
  168. :maxlength="200" />
  169. </b-field>
  170. <b-field :label="$t('settings.media.upload.uri')" label-position="on-border"
  171. :message="$t('settings.media.upload.uriHelp')">
  172. <b-input v-model="form['upload.filesystem.upload_uri']"
  173. name="app.upload_uri" placeholder='/uploads' :maxlength="200" />
  174. </b-field>
  175. </div><!-- filesystem -->
  176. <div class="block" v-if="form['upload.provider'] === 's3'">
  177. <div class="columns">
  178. <div class="column is-3">
  179. <b-field :label="$t('settings.media.s3.region')"
  180. label-position="on-border" expanded>
  181. <b-input v-model="form['upload.s3.aws_default_region']"
  182. name="upload.s3.aws_default_region"
  183. :maxlength="200" placeholder="ap-south-1" />
  184. </b-field>
  185. </div>
  186. <div class="column">
  187. <b-field grouped>
  188. <b-field :label="$t('settings.media.s3.key')"
  189. label-position="on-border" expanded>
  190. <b-input v-model="form['upload.s3.aws_access_key_id']"
  191. name="upload.s3.aws_access_key_id" :maxlength="200" />
  192. </b-field>
  193. <b-field :label="$t('settings.media.s3.secret')"
  194. label-position="on-border" expanded
  195. message="Enter a value to change.">
  196. <b-input v-model="form['upload.s3.aws_secret_access_key']"
  197. name="upload.s3.aws_secret_access_key" type="password"
  198. :maxlength="200" />
  199. </b-field>
  200. </b-field>
  201. </div>
  202. </div>
  203. <div class="columns">
  204. <div class="column is-3">
  205. <b-field :label="$t('settings.media.s3.bucketType')" label-position="on-border">
  206. <b-select v-model="form['upload.s3.bucket_type']"
  207. name="upload.s3.bucket_type" expanded>
  208. <option value="private">
  209. {{ $t('settings.media.s3.bucketTypePrivate') }}
  210. </option>
  211. <option value="public">
  212. {{ $t('settings.media.s3.bucketTypePublic') }}
  213. </option>
  214. </b-select>
  215. </b-field>
  216. </div>
  217. <div class="column">
  218. <b-field grouped>
  219. <b-field :label="$t('settings.media.s3.bucket')"
  220. label-position="on-border" expanded>
  221. <b-input v-model="form['upload.s3.bucket']"
  222. name="upload.s3.bucket" :maxlength="200" placeholder="" />
  223. </b-field>
  224. <b-field :label="$t('settings.media.s3.bucketPath')"
  225. label-position="on-border"
  226. :message="$t('settings.media.s3.bucketPathHelp')" expanded>
  227. <b-input v-model="form['upload.s3.bucket_path']"
  228. name="upload.s3.bucket_path" :maxlength="200" placeholder="/" />
  229. </b-field>
  230. </b-field>
  231. </div>
  232. </div>
  233. <div class="columns">
  234. <div class="column is-3">
  235. <b-field :label="$t('settings.media.s3.uploadExpiry')"
  236. label-position="on-border"
  237. :message="$t('settings.media.s3.uploadExpiryHelp')" expanded>
  238. <b-input v-model="form['upload.s3.expiry']"
  239. name="upload.s3.expiry"
  240. placeholder="14d" :pattern="regDuration" :maxlength="10" />
  241. </b-field>
  242. </div>
  243. </div>
  244. </div><!-- s3 -->
  245. </div>
  246. </b-tab-item><!-- media -->
  247. <b-tab-item :label="$t('settings.smtp.name')">
  248. <div class="items mail-servers">
  249. <div class="block box" v-for="(item, n) in form.smtp" :key="n">
  250. <div class="columns">
  251. <div class="column is-2">
  252. <b-field :label="$t('globals.buttons.enabled')">
  253. <b-switch v-model="item.enabled" name="enabled"
  254. :native-value="true" />
  255. </b-field>
  256. <b-field v-if="form.smtp.length > 1">
  257. <a @click.prevent="$utils.confirm(null, () => removeSMTP(n))"
  258. href="#" class="is-size-7">
  259. <b-icon icon="trash-can-outline" size="is-small" />
  260. {{ $t('globals.buttons.delete') }}
  261. </a>
  262. </b-field>
  263. </div><!-- first column -->
  264. <div class="column" :class="{'disabled': !item.enabled}">
  265. <div class="columns">
  266. <div class="column is-8">
  267. <b-field :label="$t('settings.smtp.host')" label-position="on-border"
  268. :message="$t('settings.smtp.hostHelp')">
  269. <b-input v-model="item.host" name="host"
  270. placeholder='smtp.yourmailserver.net' :maxlength="200" />
  271. </b-field>
  272. </div>
  273. <div class="column">
  274. <b-field :label="$t('settings.smtp.port')" label-position="on-border"
  275. :message="$t('settings.smtp.portHelp')">
  276. <b-numberinput v-model="item.port" name="port" type="is-light"
  277. controls-position="compact"
  278. placeholder="25" min="1" max="65535" />
  279. </b-field>
  280. </div>
  281. </div><!-- host -->
  282. <div class="columns">
  283. <div class="column is-2">
  284. <b-field :label="$t('settings.smtp.authProtocol')"
  285. label-position="on-border">
  286. <b-select v-model="item.auth_protocol" name="auth_protocol">
  287. <option value="none">none</option>
  288. <option value="cram">cram</option>
  289. <option value="plain">plain</option>
  290. <option value="login">login</option>
  291. </b-select>
  292. </b-field>
  293. </div>
  294. <div class="column">
  295. <b-field grouped>
  296. <b-field :label="$t('settings.smtp.username')"
  297. label-position="on-border" expanded>
  298. <b-input v-model="item.username"
  299. :disabled="item.auth_protocol === 'none'"
  300. name="username" placeholder="mysmtp" :maxlength="200" />
  301. </b-field>
  302. <b-field :label="$t('settings.smtp.password')"
  303. label-position="on-border" expanded
  304. :message="$t('settings.smtp.passwordHelp')">
  305. <b-input v-model="item.password"
  306. :disabled="item.auth_protocol === 'none'"
  307. name="password" type="password"
  308. :placeholder="$t('settings.smtp.passwordHelp')"
  309. :maxlength="200" />
  310. </b-field>
  311. </b-field>
  312. </div>
  313. </div><!-- auth -->
  314. <hr />
  315. <div class="columns">
  316. <div class="column is-6">
  317. <b-field :label="$t('settings.smtp.heloHost')" label-position="on-border"
  318. :message="$t('settings.smtp.heloHostHelp')">
  319. <b-input v-model="item.hello_hostname"
  320. name="hello_hostname" placeholder="" :maxlength="200" />
  321. </b-field>
  322. </div>
  323. <div class="column">
  324. <b-field grouped>
  325. <b-field :label="$t('settings.smtp.tls')" expanded
  326. :message="$t('settings.smtp.tlsHelp')">
  327. <b-switch v-model="item.tls_enabled" name="item.tls_enabled" />
  328. </b-field>
  329. <b-field :label="$t('settings.smtp.skipTLS')" expanded
  330. :message="$t('settings.smtp.skipTLSHelp')">
  331. <b-switch v-model="item.tls_skip_verify"
  332. :disabled="!item.tls_enabled" name="item.tls_skip_verify" />
  333. </b-field>
  334. </b-field>
  335. </div>
  336. </div><!-- TLS -->
  337. <hr />
  338. <div class="columns">
  339. <div class="column is-3">
  340. <b-field :label="$t('settings.smtp.maxConns')" label-position="on-border"
  341. :message="$t('settings.smtp.maxConnsHelp')">
  342. <b-numberinput v-model="item.max_conns" name="max_conns" type="is-light"
  343. controls-position="compact"
  344. placeholder="25" min="1" max="65535" />
  345. </b-field>
  346. </div>
  347. <div class="column is-3">
  348. <b-field :label="$t('settings.smtp.retries')" label-position="on-border"
  349. :message="$t('settings.smtp.retriesHelp')">
  350. <b-numberinput v-model="item.max_msg_retries" name="max_msg_retries"
  351. type="is-light"
  352. controls-position="compact"
  353. placeholder="2" min="1" max="1000" />
  354. </b-field>
  355. </div>
  356. <div class="column is-3">
  357. <b-field :label="$t('settings.smtp.idleTimeout')" label-position="on-border"
  358. :message="$t('settings.smtp.idleTimeoutHelp')">
  359. <b-input v-model="item.idle_timeout" name="idle_timeout"
  360. placeholder="15s" :pattern="regDuration" :maxlength="10" />
  361. </b-field>
  362. </div>
  363. <div class="column is-3">
  364. <b-field :label="$t('settings.smtp.waitTimeout')" label-position="on-border"
  365. :message="$t('settings.smtp.waitTimeoutHelp')">
  366. <b-input v-model="item.wait_timeout" name="wait_timeout"
  367. placeholder="5s" :pattern="regDuration" :maxlength="10" />
  368. </b-field>
  369. </div>
  370. </div>
  371. <hr />
  372. <div>
  373. <p v-if="item.email_headers.length === 0 && !item.showHeaders">
  374. <a href="#" class="is-size-7" @click.prevent="() => showSMTPHeaders(n)">
  375. <b-icon icon="plus" />{{ $t('settings.smtp.setCustomHeaders') }}</a>
  376. </p>
  377. <b-field v-if="item.email_headers.length > 0 || item.showHeaders"
  378. :label="$t('')" label-position="on-border"
  379. :message="$t('settings.smtp.customHeadersHelp')">
  380. <b-input v-model="item.strEmailHeaders" name="email_headers" type="textarea"
  381. placeholder='[{"X-Custom": "value"}, {"X-Custom2": "value"}]' />
  382. </b-field>
  383. </div>
  384. </div>
  385. </div><!-- second container column -->
  386. </div><!-- block -->
  387. </div><!-- mail-servers -->
  388. <b-button @click="addSMTP" icon-left="plus" type="is-primary">
  389. {{ $t('globals.buttons.addNew') }}
  390. </b-button>
  391. </b-tab-item><!-- mail servers -->
  392. <b-tab-item :label="$t('settings.messengers.name')">
  393. <div class="items messengers">
  394. <div class="block box" v-for="(item, n) in form.messengers" :key="n">
  395. <div class="columns">
  396. <div class="column is-2">
  397. <b-field :label="$t('globals.buttons.enabled')">
  398. <b-switch v-model="item.enabled" name="enabled"
  399. :native-value="true" />
  400. </b-field>
  401. <b-field>
  402. <a @click.prevent="$utils.confirm(null, () => removeMessenger(n))"
  403. href="#" class="is-size-7">
  404. <b-icon icon="trash-can-outline" size="is-small" />
  405. {{ $t('globals.buttons.delete') }}
  406. </a>
  407. </b-field>
  408. </div><!-- first column -->
  409. <div class="column" :class="{'disabled': !item.enabled}">
  410. <div class="columns">
  411. <div class="column is-4">
  412. <b-field :label="$t('globals.fields.name')" label-position="on-border"
  413. :message="$t('settings.messengers.nameHelp')">
  414. <b-input v-model="item.name" name="name"
  415. placeholder='mymessenger' :maxlength="200" />
  416. </b-field>
  417. </div>
  418. <div class="column is-8">
  419. <b-field :label="$t('settings.messengers.url')" label-position="on-border"
  420. :message="$t('settings.messengers.urlHelp')">
  421. <b-input v-model="item.root_url" name="root_url"
  422. placeholder='https://postback.messenger.net/path' :maxlength="200" />
  423. </b-field>
  424. </div>
  425. </div><!-- host -->
  426. <div class="columns">
  427. <div class="column">
  428. <b-field grouped>
  429. <b-field :label="$t('settings.messengers.username')"
  430. label-position="on-border" expanded>
  431. <b-input v-model="item.username" name="username" :maxlength="200" />
  432. </b-field>
  433. <b-field :label="$t('settings.messengers.password')"
  434. label-position="on-border" expanded
  435. :message="$t('globals.messages.passwordChange')">
  436. <b-input v-model="item.password"
  437. name="password" type="password"
  438. :placeholder="$t('globals.messages.passwordChange')"
  439. :maxlength="200" />
  440. </b-field>
  441. </b-field>
  442. </div>
  443. </div><!-- auth -->
  444. <hr />
  445. <div class="columns">
  446. <div class="column is-4">
  447. <b-field :label="$t('settings.messengers.maxConns')"
  448. label-position="on-border"
  449. :message="$t('settings.messengers.maxConnsHelp')">
  450. <b-numberinput v-model="item.max_conns" name="max_conns" type="is-light"
  451. controls-position="compact"
  452. placeholder="25" min="1" max="65535" />
  453. </b-field>
  454. </div>
  455. <div class="column is-4">
  456. <b-field :label="$t('settings.messengers.retries')"
  457. label-position="on-border"
  458. :message="$t('settings.messengers.retriesHelp')">
  459. <b-numberinput v-model="item.max_msg_retries" name="max_msg_retries"
  460. type="is-light"
  461. controls-position="compact"
  462. placeholder="2" min="1" max="1000" />
  463. </b-field>
  464. </div>
  465. <div class="column is-4">
  466. <b-field :label="$t('settings.messengers.timeout')"
  467. label-position="on-border"
  468. :message="$t('settings.messengers.timeoutHelp')">
  469. <b-input v-model="item.timeout" name="timeout"
  470. placeholder="5s" :pattern="regDuration" :maxlength="10" />
  471. </b-field>
  472. </div>
  473. </div>
  474. <hr />
  475. </div>
  476. </div><!-- second container column -->
  477. </div><!-- block -->
  478. </div><!-- mail-servers -->
  479. <b-button @click="addMessenger" icon-left="plus" type="is-primary">
  480. {{ $t('globals.buttons.addNew') }}
  481. </b-button>
  482. </b-tab-item><!-- messengers -->
  483. </b-tabs>
  484. </form>
  485. </section>
  486. </section>
  487. </template>
  488. <script>
  489. import Vue from 'vue';
  490. import { mapState } from 'vuex';
  491. import store from '../store';
  492. import { models } from '../constants';
  493. const dummyPassword = ' '.repeat(8);
  494. export default Vue.extend({
  495. data() {
  496. return {
  497. regDuration: '[0-9]+(ms|s|m|h|d)',
  498. isLoading: true,
  499. // formCopy is a stringified copy of the original settings against which
  500. // form is compared to detect changes.
  501. formCopy: '',
  502. form: {},
  503. };
  504. },
  505. methods: {
  506. addSMTP() {
  507. this.form.smtp.push({
  508. enabled: true,
  509. host: '',
  510. hello_hostname: '',
  511. port: 587,
  512. auth_protocol: 'none',
  513. username: '',
  514. password: '',
  515. email_headers: [],
  516. max_conns: 10,
  517. max_msg_retries: 2,
  518. idle_timeout: '15s',
  519. wait_timeout: '5s',
  520. tls_enabled: true,
  521. tls_skip_verify: false,
  522. });
  523. this.$nextTick(() => {
  524. const items = document.querySelectorAll('.mail-servers input[name="host"]');
  525. items[items.length - 1].focus();
  526. });
  527. },
  528. removeSMTP(i) {
  529. this.form.smtp.splice(i, 1);
  530. },
  531. showSMTPHeaders(i) {
  532. const s = this.form.smtp[i];
  533. s.showHeaders = true;
  534. this.form.smtp.splice(i, 1, s);
  535. },
  536. addMessenger() {
  537. this.form.messengers.push({
  538. enabled: true,
  539. root_url: '',
  540. name: '',
  541. username: '',
  542. password: '',
  543. max_conns: 25,
  544. max_msg_retries: 2,
  545. timeout: '5s',
  546. });
  547. this.$nextTick(() => {
  548. const items = document.querySelectorAll('.messengers input[name="name"]');
  549. items[items.length - 1].focus();
  550. });
  551. },
  552. removeMessenger(i) {
  553. this.form.messengers.splice(i, 1);
  554. },
  555. onSubmit() {
  556. const form = JSON.parse(JSON.stringify(this.form));
  557. // De-serialize custom e-mail headers.
  558. for (let i = 0; i < form.smtp.length; i += 1) {
  559. // If it's the dummy UI password placeholder, ignore it.
  560. if (form.smtp[i].password === dummyPassword) {
  561. form.smtp[i].password = '';
  562. }
  563. if (form.smtp[i].strEmailHeaders && form.smtp[i].strEmailHeaders !== '[]') {
  564. form.smtp[i].email_headers = JSON.parse(form.smtp[i].strEmailHeaders);
  565. } else {
  566. form.smtp[i].email_headers = [];
  567. }
  568. }
  569. if (form['upload.s3.aws_secret_access_key'] === dummyPassword) {
  570. form['upload.s3.aws_secret_access_key'] = '';
  571. }
  572. for (let i = 0; i < form.messengers.length; i += 1) {
  573. // If it's the dummy UI password placeholder, ignore it.
  574. if (form.messengers[i].password === dummyPassword) {
  575. form.messengers[i].password = '';
  576. }
  577. }
  578. this.isLoading = true;
  579. this.$api.updateSettings(form).then((data) => {
  580. if (data.needsRestart) {
  581. // Update the 'needsRestart' flag on the global serverConfig state
  582. // as there are running campaigns and the app couldn't auto-restart.
  583. store.commit('setModelResponse',
  584. { model: models.serverConfig, data: { ...this.serverConfig, needsRestart: true } });
  585. this.getSettings();
  586. return;
  587. }
  588. this.$utils.toast(this.$t('settings.messengers.messageSaved'));
  589. // Poll until there's a 200 response, waiting for the app
  590. // to restart and come back up.
  591. const pollId = setInterval(() => {
  592. this.$api.getHealth().then(() => {
  593. clearInterval(pollId);
  594. this.getSettings();
  595. this.$reloadServerConfig();
  596. });
  597. }, 500);
  598. }, () => {
  599. this.isLoading = false;
  600. });
  601. },
  602. getSettings() {
  603. this.$api.getSettings().then((data) => {
  604. const d = data;
  605. // Serialize the `email_headers` array map to display on the form.
  606. for (let i = 0; i < d.smtp.length; i += 1) {
  607. d.smtp[i].strEmailHeaders = JSON.stringify(d.smtp[i].email_headers, null, 4);
  608. // The backend doesn't send passwords, so add a dummy so that it
  609. // the password looks filled on the UI.
  610. d.smtp[i].password = dummyPassword;
  611. }
  612. for (let i = 0; i < d.messengers.length; i += 1) {
  613. // The backend doesn't send passwords, so add a dummy so that it
  614. // the password looks filled on the UI.
  615. d.messengers[i].password = dummyPassword;
  616. }
  617. if (d['upload.provider'] === 's3') {
  618. d['upload.s3.aws_secret_access_key'] = dummyPassword;
  619. }
  620. this.form = d;
  621. this.formCopy = JSON.stringify(d);
  622. this.isLoading = false;
  623. });
  624. },
  625. },
  626. computed: {
  627. ...mapState(['serverConfig', 'loading']),
  628. hasFormChanged() {
  629. if (!this.formCopy) {
  630. return false;
  631. }
  632. return JSON.stringify(this.form) !== this.formCopy;
  633. },
  634. },
  635. beforeRouteLeave(to, from, next) {
  636. if (this.hasFormChanged) {
  637. this.$utils.confirm(this.$t('settings.messengers.messageDiscard'), () => next(true));
  638. return;
  639. }
  640. next(true);
  641. },
  642. mounted() {
  643. this.getSettings();
  644. },
  645. });
  646. </script>