Domains.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. <template>
  2. <div>
  3. <div class="mb-6 flex flex-col md:flex-row justify-between md:items-center">
  4. <div class="relative">
  5. <input
  6. v-model="search"
  7. @keyup.esc="search = ''"
  8. tabindex="0"
  9. type="text"
  10. class="w-full md:w-64 appearance-none shadow bg-white text-grey-700 focus:outline-none rounded py-3 pl-3 pr-8"
  11. placeholder="Search Domains"
  12. />
  13. <icon
  14. v-if="search"
  15. @click.native="search = ''"
  16. name="close-circle"
  17. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
  18. />
  19. <icon
  20. v-else
  21. name="search"
  22. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current pointer-events-none mr-2 flex items-center"
  23. />
  24. </div>
  25. <div class="mt-4 md:mt-0">
  26. <button
  27. @click="addDomainModalOpen = true"
  28. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none ml-auto"
  29. >
  30. Add Custom Domain
  31. </button>
  32. </div>
  33. </div>
  34. <vue-good-table
  35. v-if="initialDomains.length"
  36. @on-search="debounceToolips"
  37. :columns="columns"
  38. :rows="rows"
  39. :search-options="{
  40. enabled: true,
  41. skipDiacritics: true,
  42. externalQuery: search,
  43. }"
  44. :sort-options="{
  45. enabled: true,
  46. initialSortBy: { field: 'created_at', type: 'desc' },
  47. }"
  48. styleClass="vgt-table"
  49. >
  50. <div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
  51. No domains found for that search!
  52. </div>
  53. <template slot="table-row" slot-scope="props">
  54. <span
  55. v-if="props.column.field == 'created_at'"
  56. class="tooltip outline-none text-sm"
  57. :data-tippy-content="props.row.created_at | formatDate"
  58. >{{ props.row.created_at | timeAgo }}
  59. </span>
  60. <span v-else-if="props.column.field == 'domain'">
  61. <span
  62. class="tooltip cursor-pointer outline-none"
  63. data-tippy-content="Click to copy"
  64. v-clipboard="() => props.row.domain"
  65. v-clipboard:success="clipboardSuccess"
  66. v-clipboard:error="clipboardError"
  67. >{{ props.row.domain | truncate(30) }}</span
  68. >
  69. </span>
  70. <span v-else-if="props.column.field == 'description'">
  71. <div v-if="domainIdToEdit === props.row.id" class="flex items-center">
  72. <input
  73. @keyup.enter="editDomain(rows[props.row.originalIndex])"
  74. @keyup.esc="domainIdToEdit = domainDescriptionToEdit = ''"
  75. v-model="domainDescriptionToEdit"
  76. type="text"
  77. class="flex-grow appearance-none bg-grey-100 border text-grey-700 focus:outline-none rounded px-2 py-1"
  78. :class="
  79. domainDescriptionToEdit.length > 100 ? 'border-red-500' : 'border-transparent'
  80. "
  81. placeholder="Add description"
  82. tabindex="0"
  83. autofocus
  84. />
  85. <icon
  86. name="close"
  87. class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
  88. @click.native="domainIdToEdit = domainDescriptionToEdit = ''"
  89. />
  90. <icon
  91. name="save"
  92. class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
  93. @click.native="editDomain(rows[props.row.originalIndex])"
  94. />
  95. </div>
  96. <div v-else-if="props.row.description" class="flex items-centers">
  97. <span class="tooltip outline-none" :data-tippy-content="props.row.description">{{
  98. props.row.description | truncate(60)
  99. }}</span>
  100. <icon
  101. name="edit"
  102. class="inline-block w-6 h-6 text-grey-200 fill-current cursor-pointer ml-2"
  103. @click.native="
  104. ;(domainIdToEdit = props.row.id), (domainDescriptionToEdit = props.row.description)
  105. "
  106. />
  107. </div>
  108. <div v-else class="flex justify-center">
  109. <icon
  110. name="plus"
  111. class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
  112. @click.native=";(domainIdToEdit = props.row.id), (domainDescriptionToEdit = '')"
  113. />
  114. </div>
  115. </span>
  116. <span v-else-if="props.column.field === 'default_recipient'">
  117. <div v-if="props.row.default_recipient">
  118. {{ props.row.default_recipient.email | truncate(30) }}
  119. <icon
  120. name="edit"
  121. class="ml-2 inline-block w-6 h-6 text-grey-200 fill-current cursor-pointer"
  122. @click.native="openDomainDefaultRecipientModal(props.row)"
  123. />
  124. </div>
  125. <div class="flex justify-center" v-else>
  126. <icon
  127. name="plus"
  128. class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
  129. @click.native="openDomainDefaultRecipientModal(props.row)"
  130. />
  131. </div>
  132. </span>
  133. <span v-else-if="props.column.field === 'aliases_count'">
  134. {{ props.row.aliases.length }}
  135. </span>
  136. <span v-else-if="props.column.field === 'active'" class="flex items-center">
  137. <Toggle
  138. v-model="rows[props.row.originalIndex].active"
  139. @on="activateDomain(props.row.id)"
  140. @off="deactivateDomain(props.row.id)"
  141. />
  142. </span>
  143. <span v-else-if="props.column.field === 'domain_sending_verified_at'">
  144. <span
  145. name="check"
  146. v-if="props.row.domain_sending_verified_at"
  147. class="py-1 px-2 bg-green-200 text-green-900 rounded-full text-sm"
  148. >
  149. verified
  150. </span>
  151. <button
  152. v-else
  153. @click="openCheckRecordsModal(rows[props.row.originalIndex])"
  154. class="focus:outline-none text-sm"
  155. >
  156. Check Records
  157. </button>
  158. </span>
  159. <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
  160. <icon
  161. name="trash"
  162. class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
  163. @click.native="openDeleteModal(props.row.id)"
  164. />
  165. </span>
  166. </template>
  167. </vue-good-table>
  168. <div v-else class="bg-white rounded shadow overflow-x-auto">
  169. <div class="p-8 text-center text-lg text-grey-700">
  170. <h1 class="mb-6 text-xl text-indigo-800 font-semibold">
  171. This is where you can set up and view custom domains
  172. </h1>
  173. <div class="mx-auto mb-6 w-24 border-b-2 border-grey-200"></div>
  174. <p class="mb-4">
  175. To get started all you have to do is add a TXT record to your domain to verify ownership
  176. and then add the domain here by clicking the button above.
  177. </p>
  178. <p class="mb-4">
  179. The TXT record needs to have the following values:
  180. </p>
  181. <p class="mb-4">
  182. Type: <b>TXT</b><br />
  183. Host: <b>@</b><br />
  184. Value: <b>{{ aaVerify }}</b
  185. ><br />
  186. </p>
  187. <p>
  188. Once the DNS changes propagate and you have verified ownership of the domain you will need
  189. to add a few more records to be able to recieve emails at your own domain.
  190. </p>
  191. </div>
  192. </div>
  193. <Modal :open="addDomainModalOpen" @close="closeCheckRecordsModal">
  194. <div v-if="!domainToCheck" class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6">
  195. <h2
  196. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  197. >
  198. Add new domain
  199. </h2>
  200. <p class="mt-4 mb-2 text-grey-700">
  201. To verify ownership of the domain, please add the following TXT record and then click Add
  202. Domain below.
  203. </p>
  204. <div class="table w-full">
  205. <div class="table-row">
  206. <div class="table-cell py-2 font-semibold">Type</div>
  207. <div class="table-cell p-2 font-semibold">Host</div>
  208. <div class="table-cell py-2 font-semibold">Value/Points to</div>
  209. </div>
  210. <div class="table-row">
  211. <div class="table-cell py-2">TXT</div>
  212. <div class="table-cell p-2">@</div>
  213. <div class="table-cell py-2 break-all">aa-verify={{ aaVerify }}</div>
  214. </div>
  215. </div>
  216. <div class="mt-6">
  217. <p v-show="errors.newDomain" class="mb-3 text-red-500 text-sm">
  218. {{ errors.newDomain }}
  219. </p>
  220. <input
  221. v-model="newDomain"
  222. type="text"
  223. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  224. :class="errors.newDomain ? 'border-red-500' : ''"
  225. placeholder="example.com"
  226. autofocus
  227. />
  228. <button
  229. @click="validateNewDomain"
  230. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  231. :class="addDomainLoading ? 'cursor-not-allowed' : ''"
  232. :disabled="addDomainLoading"
  233. >
  234. Add Domain
  235. <loader v-if="addDomainLoading" />
  236. </button>
  237. <button
  238. @click="addDomainModalOpen = false"
  239. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  240. >
  241. Cancel
  242. </button>
  243. </div>
  244. </div>
  245. <div v-else class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6">
  246. <h2
  247. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  248. >
  249. Check DNS records
  250. </h2>
  251. <p class="mt-4 mb-2 text-grey-700">
  252. Please set the following DNS records for your custom domain. If you have more than one MX
  253. record then the MX record below should have the lowest priority (e.g. 10).
  254. </p>
  255. <div class="table w-full">
  256. <div class="table-row">
  257. <div class="table-cell py-2 font-semibold">Type</div>
  258. <div class="table-cell py-2 px-4 font-semibold">Host</div>
  259. <div class="table-cell py-2 font-semibold">Value/Points to</div>
  260. </div>
  261. <div class="table-row">
  262. <div class="table-cell py-2">MX</div>
  263. <div class="table-cell py-2 px-4">@</div>
  264. <div class="table-cell py-2 break-words">{{ hostname }}</div>
  265. </div>
  266. <div class="table-row">
  267. <div class="table-cell py-2">TXT</div>
  268. <div class="table-cell py-2 px-4">@</div>
  269. <div class="table-cell py-2 break-words">v=spf1 include:spf.{{ domainName }} -all</div>
  270. </div>
  271. <div class="table-row">
  272. <div class="table-cell py-2">CNAME</div>
  273. <div class="table-cell py-2 px-4">dk1._domainkey</div>
  274. <div class="table-cell py-2 break-words">dk1._domainkey.{{ domainName }}</div>
  275. </div>
  276. <div class="table-row">
  277. <div class="table-cell py-2">CNAME</div>
  278. <div class="table-cell py-2 px-4">dk2._domainkey</div>
  279. <div class="table-cell py-2 break-words">dk2._domainkey.{{ domainName }}</div>
  280. </div>
  281. <div class="table-row">
  282. <div class="table-cell py-2">TXT</div>
  283. <div class="table-cell py-2 px-4">@</div>
  284. <div class="table-cell py-2 break-words">v=DMARC1; p=quarantine; adkim=s</div>
  285. </div>
  286. </div>
  287. <div class="mt-6">
  288. <button
  289. @click="checkRecords(domainToCheck)"
  290. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  291. :class="checkRecordsLoading ? 'cursor-not-allowed' : ''"
  292. :disabled="checkRecordsLoading"
  293. >
  294. Check Records
  295. <loader v-if="checkRecordsLoading" />
  296. </button>
  297. <button
  298. @click="closeCheckRecordsModal"
  299. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  300. >
  301. Cancel
  302. </button>
  303. </div>
  304. </div>
  305. </Modal>
  306. <Modal :open="domainDefaultRecipientModalOpen" @close="closeDomainDefaultRecipientModal">
  307. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
  308. <h2
  309. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  310. >
  311. Update Default Recipient
  312. </h2>
  313. <p class="my-4 text-grey-700">
  314. Select the default recipient for this domain. This overrides the default recipient in your
  315. account settings. Leave it empty if you would like to use the default recipient in your
  316. account settings.
  317. </p>
  318. <multiselect
  319. v-model="defaultRecipient"
  320. :options="recipientOptions"
  321. :multiple="false"
  322. :close-on-select="true"
  323. :clear-on-select="false"
  324. :searchable="false"
  325. :allow-empty="true"
  326. placeholder="Select recipient"
  327. label="email"
  328. track-by="email"
  329. :preselect-first="false"
  330. :show-labels="false"
  331. >
  332. </multiselect>
  333. <div class="mt-6">
  334. <button
  335. type="button"
  336. @click="editDefaultRecipient()"
  337. class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
  338. :class="editDefaultRecipientLoading ? 'cursor-not-allowed' : ''"
  339. :disabled="editDefaultRecipientLoading"
  340. >
  341. Update Default Recipient
  342. <loader v-if="editDefaultRecipientLoading" />
  343. </button>
  344. <button
  345. @click="closeDomainDefaultRecipientModal()"
  346. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  347. >
  348. Cancel
  349. </button>
  350. </div>
  351. </div>
  352. </Modal>
  353. <Modal :open="deleteDomainModalOpen" @close="closeDeleteModal">
  354. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
  355. <h2
  356. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  357. >
  358. Delete domain
  359. </h2>
  360. <p class="mt-4 text-grey-700">
  361. Are you sure you want to delete this domain? You will no longer be able to receive any
  362. emails at this domain.
  363. </p>
  364. <div class="mt-6">
  365. <button
  366. type="button"
  367. @click="deleteDomain(domainIdToDelete)"
  368. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  369. :class="deleteDomainLoading ? 'cursor-not-allowed' : ''"
  370. :disabled="deleteDomainLoading"
  371. >
  372. Delete domain
  373. <loader v-if="deleteDomainLoading" />
  374. </button>
  375. <button
  376. @click="closeDeleteModal"
  377. class="ml-4 px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  378. >
  379. Cancel
  380. </button>
  381. </div>
  382. </div>
  383. </Modal>
  384. </div>
  385. </template>
  386. <script>
  387. import Modal from './../components/Modal.vue'
  388. import Toggle from './../components/Toggle.vue'
  389. import tippy from 'tippy.js'
  390. import Multiselect from 'vue-multiselect'
  391. export default {
  392. props: {
  393. initialDomains: {
  394. type: Array,
  395. required: true,
  396. },
  397. domainName: {
  398. type: String,
  399. required: true,
  400. },
  401. hostname: {
  402. type: String,
  403. required: true,
  404. },
  405. recipientOptions: {
  406. type: Array,
  407. required: true,
  408. },
  409. aaVerify: {
  410. type: String,
  411. required: true,
  412. },
  413. },
  414. components: {
  415. Modal,
  416. Toggle,
  417. Multiselect,
  418. },
  419. mounted() {
  420. this.addTooltips()
  421. },
  422. data() {
  423. return {
  424. newDomain: '',
  425. search: '',
  426. addDomainLoading: false,
  427. addDomainModalOpen: false,
  428. domainIdToDelete: null,
  429. domainIdToEdit: null,
  430. domainDescriptionToEdit: '',
  431. domainToCheck: null,
  432. deleteDomainLoading: false,
  433. deleteDomainModalOpen: false,
  434. checkRecordsLoading: false,
  435. domainDefaultRecipientModalOpen: false,
  436. defaultRecipientDomainToEdit: {},
  437. defaultRecipient: {},
  438. editDefaultRecipientLoading: false,
  439. errors: {},
  440. columns: [
  441. {
  442. label: 'Created',
  443. field: 'created_at',
  444. globalSearchDisabled: true,
  445. },
  446. {
  447. label: 'Domain',
  448. field: 'domain',
  449. },
  450. {
  451. label: 'Description',
  452. field: 'description',
  453. },
  454. {
  455. label: 'Default Recipient',
  456. field: 'default_recipient',
  457. sortable: false,
  458. globalSearchDisabled: true,
  459. },
  460. {
  461. label: 'Alias Count',
  462. field: 'aliases_count',
  463. type: 'number',
  464. globalSearchDisabled: true,
  465. },
  466. {
  467. label: 'Active',
  468. field: 'active',
  469. type: 'boolean',
  470. globalSearchDisabled: true,
  471. },
  472. {
  473. label: 'Verified for Sending',
  474. field: 'domain_sending_verified_at',
  475. globalSearchDisabled: true,
  476. },
  477. {
  478. label: '',
  479. field: 'actions',
  480. sortable: false,
  481. globalSearchDisabled: true,
  482. },
  483. ],
  484. rows: this.initialDomains,
  485. }
  486. },
  487. watch: {
  488. domainIdToEdit: _.debounce(function() {
  489. this.addTooltips()
  490. }, 50),
  491. },
  492. methods: {
  493. addTooltips() {
  494. tippy('.tooltip', {
  495. arrow: true,
  496. arrowType: 'round',
  497. })
  498. },
  499. debounceToolips: _.debounce(function() {
  500. this.addTooltips()
  501. }, 50),
  502. validateNewDomain(e) {
  503. this.errors = {}
  504. if (!this.newDomain) {
  505. this.errors.newDomain = 'Domain name required'
  506. } else if (!this.validDomain(this.newDomain)) {
  507. this.errors.newDomain = 'Please enter a valid domain name'
  508. }
  509. if (!this.errors.newDomain) {
  510. this.addNewDomain()
  511. }
  512. e.preventDefault()
  513. },
  514. addNewDomain() {
  515. this.addDomainLoading = true
  516. axios
  517. .post(
  518. '/api/v1/domains',
  519. JSON.stringify({
  520. domain: this.newDomain,
  521. }),
  522. {
  523. headers: { 'Content-Type': 'application/json' },
  524. }
  525. )
  526. .then(({ data }) => {
  527. this.addDomainLoading = false
  528. this.rows.push(data.data)
  529. this.newDomain = ''
  530. this.domainToCheck = data.data
  531. this.success('Custom domain added')
  532. })
  533. .catch(error => {
  534. this.addDomainLoading = false
  535. if (error.response.status === 422) {
  536. this.error(error.response.data.errors.domain[0])
  537. } else if (error.response.status === 429) {
  538. this.error('Please wait a little while before checking the records again')
  539. } else if (error.response.status === 404) {
  540. this.warn(
  541. 'Verification TXT record not found, this could be due to DNS caching, please try again shortly.'
  542. )
  543. } else {
  544. this.error()
  545. }
  546. })
  547. },
  548. checkRecords(domain) {
  549. this.checkRecordsLoading = true
  550. axios
  551. .get(`/domains/${domain.id}/check-sending`)
  552. .then(({ data }) => {
  553. this.checkRecordsLoading = false
  554. if (data.success === true) {
  555. this.closeCheckRecordsModal()
  556. this.success(data.message)
  557. domain.domain_sending_verified_at = data.data.domain_sending_verified_at
  558. } else {
  559. this.warn(data.message)
  560. }
  561. })
  562. .catch(error => {
  563. this.checkRecordsLoading = false
  564. if (error.response.status === 429) {
  565. this.error('Please wait a little while before checking the records again')
  566. } else {
  567. this.error()
  568. }
  569. })
  570. },
  571. openDeleteModal(id) {
  572. this.deleteDomainModalOpen = true
  573. this.domainIdToDelete = id
  574. },
  575. closeDeleteModal() {
  576. this.deleteDomainModalOpen = false
  577. this.domainIdToDelete = null
  578. },
  579. openDomainDefaultRecipientModal(domain) {
  580. this.domainDefaultRecipientModalOpen = true
  581. this.defaultRecipientDomainToEdit = domain
  582. this.defaultRecipient = domain.default_recipient
  583. },
  584. closeDomainDefaultRecipientModal() {
  585. this.domainDefaultRecipientModalOpen = false
  586. this.defaultRecipientDomainToEdit = {}
  587. this.defaultRecipient = {}
  588. },
  589. openCheckRecordsModal(domain) {
  590. this.domainToCheck = domain
  591. this.addDomainModalOpen = true
  592. },
  593. closeCheckRecordsModal() {
  594. this.domainToCheck = null
  595. this.addDomainModalOpen = false
  596. },
  597. editDomain(domain) {
  598. if (this.domainDescriptionToEdit.length > 100) {
  599. return this.error('Description cannot be more than 100 characters')
  600. }
  601. axios
  602. .patch(
  603. `/api/v1/domains/${domain.id}`,
  604. JSON.stringify({
  605. description: this.domainDescriptionToEdit,
  606. }),
  607. {
  608. headers: { 'Content-Type': 'application/json' },
  609. }
  610. )
  611. .then(response => {
  612. domain.description = this.domainDescriptionToEdit
  613. this.domainIdToEdit = null
  614. this.domainDescriptionToEdit = ''
  615. this.success('Domain description updated')
  616. })
  617. .catch(error => {
  618. this.domainIdToEdit = null
  619. this.domainDescriptionToEdit = ''
  620. this.error()
  621. })
  622. },
  623. editDefaultRecipient() {
  624. this.editDefaultRecipientLoading = true
  625. axios
  626. .patch(
  627. `/api/v1/domains/${this.defaultRecipientDomainToEdit.id}/default-recipient`,
  628. JSON.stringify({
  629. default_recipient: this.defaultRecipient ? this.defaultRecipient.id : '',
  630. }),
  631. {
  632. headers: { 'Content-Type': 'application/json' },
  633. }
  634. )
  635. .then(response => {
  636. let domain = _.find(this.rows, ['id', this.defaultRecipientDomainToEdit.id])
  637. domain.default_recipient = this.defaultRecipient
  638. this.domainDefaultRecipientModalOpen = false
  639. this.editDefaultRecipientLoading = false
  640. this.defaultRecipient = {}
  641. this.success("Domain's default recipient updated")
  642. })
  643. .catch(error => {
  644. this.domainDefaultRecipientModalOpen = false
  645. this.editDefaultRecipientLoading = false
  646. this.defaultRecipient = {}
  647. this.error()
  648. })
  649. },
  650. activateDomain(id) {
  651. axios
  652. .post(
  653. `/api/v1/active-domains`,
  654. JSON.stringify({
  655. id: id,
  656. }),
  657. {
  658. headers: { 'Content-Type': 'application/json' },
  659. }
  660. )
  661. .then(response => {
  662. //
  663. })
  664. .catch(error => {
  665. this.error()
  666. })
  667. },
  668. deactivateDomain(id) {
  669. axios
  670. .delete(`/api/v1/active-domains/${id}`)
  671. .then(response => {
  672. //
  673. })
  674. .catch(error => {
  675. this.error()
  676. })
  677. },
  678. deleteDomain(id) {
  679. this.deleteDomainLoading = true
  680. axios
  681. .delete(`/api/v1/domains/${id}`)
  682. .then(response => {
  683. this.rows = _.reject(this.rows, domain => domain.id === id)
  684. this.deleteDomainModalOpen = false
  685. this.deleteDomainLoading = false
  686. })
  687. .catch(error => {
  688. this.error()
  689. this.deleteDomainLoading = false
  690. this.deleteDomainModalOpen = false
  691. })
  692. },
  693. validDomain(domain) {
  694. let re = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/
  695. return re.test(domain)
  696. },
  697. clipboardSuccess() {
  698. this.success('Copied to clipboard')
  699. },
  700. clipboardError() {
  701. this.error('Could not copy to clipboard')
  702. },
  703. warn(text = '') {
  704. this.$notify({
  705. title: 'Information',
  706. text: text,
  707. type: 'warn',
  708. })
  709. },
  710. success(text = '') {
  711. this.$notify({
  712. title: 'Success',
  713. text: text,
  714. type: 'success',
  715. })
  716. },
  717. error(text = 'An error has occurred, please try again later') {
  718. this.$notify({
  719. title: 'Error',
  720. text: text,
  721. type: 'error',
  722. })
  723. },
  724. },
  725. }
  726. </script>