Domains.vue 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844
  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="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. v-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. <template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
  51. No domains found for that search!
  52. </template>
  53. <template #table-row="props">
  54. <span
  55. v-if="props.column.field == 'created_at'"
  56. class="tooltip outline-none text-sm"
  57. :data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
  58. >{{ $filters.timeAgo(props.row.created_at) }}
  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="() => rows[props.row.originalIndex].domain"
  65. v-clipboard:success="clipboardSuccess"
  66. v-clipboard:error="clipboardError"
  67. >{{ $filters.truncate(props.row.domain, 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="grow appearance-none bg-grey-100 border text-grey-700 focus:outline-none rounded px-2 py-1"
  78. :class="
  79. domainDescriptionToEdit.length > 200 ? '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="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="editDomain(rows[props.row.originalIndex])"
  94. />
  95. </div>
  96. <div v-else-if="props.row.description" class="flex items-centers">
  97. <span class="outline-none">{{ $filters.truncate(props.row.description, 60) }}</span>
  98. <icon
  99. name="edit"
  100. class="inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer ml-2"
  101. @click="
  102. ;(domainIdToEdit = props.row.id), (domainDescriptionToEdit = props.row.description)
  103. "
  104. />
  105. </div>
  106. <div v-else class="flex justify-center">
  107. <icon
  108. name="plus"
  109. class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
  110. @click=";(domainIdToEdit = props.row.id), (domainDescriptionToEdit = '')"
  111. />
  112. </div>
  113. </span>
  114. <span v-else-if="props.column.field === 'default_recipient'">
  115. <div v-if="props.row.default_recipient">
  116. {{ $filters.truncate(props.row.default_recipient.email, 30) }}
  117. <icon
  118. name="edit"
  119. class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
  120. @click="openDomainDefaultRecipientModal(props.row)"
  121. />
  122. </div>
  123. <div class="flex justify-center" v-else>
  124. <icon
  125. name="plus"
  126. class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
  127. @click="openDomainDefaultRecipientModal(props.row)"
  128. />
  129. </div>
  130. </span>
  131. <span v-else-if="props.column.field === 'aliases_count'">
  132. {{ props.row.aliases_count }}
  133. </span>
  134. <span v-else-if="props.column.field === 'active'" class="flex items-center">
  135. <Toggle
  136. v-model="rows[props.row.originalIndex].active"
  137. @on="activateDomain(props.row.id)"
  138. @off="deactivateDomain(props.row.id)"
  139. />
  140. </span>
  141. <span v-else-if="props.column.field === 'catch_all'" class="flex items-center">
  142. <Toggle
  143. v-model="rows[props.row.originalIndex].catch_all"
  144. @on="enableCatchAll(props.row.id)"
  145. @off="disableCatchAll(props.row.id)"
  146. />
  147. </span>
  148. <span v-else-if="props.column.field === 'domain_sending_verified_at'">
  149. <div v-if="props.row.domain_sending_verified_at || props.row.domain_mx_validated_at">
  150. <svg
  151. v-if="props.row.domain_sending_verified_at && props.row.domain_mx_validated_at"
  152. class="h-5 w-5 inline-block"
  153. xmlns="http://www.w3.org/2000/svg"
  154. viewBox="0 0 20 20"
  155. >
  156. <g fill="none" fill-rule="evenodd">
  157. <circle class="text-green-200 fill-current" cx="10" cy="10" r="10"></circle>
  158. <polyline
  159. class="text-green-900 stroke-current"
  160. stroke-linecap="round"
  161. stroke-linejoin="round"
  162. stroke-width="2"
  163. points="6 10 8.667 12.667 14 7.333"
  164. ></polyline>
  165. </g>
  166. </svg>
  167. <svg
  168. v-else-if="!props.row.domain_mx_validated_at"
  169. xmlns="http://www.w3.org/2000/svg"
  170. viewBox="0 0 20 20"
  171. class="h-5 w-5 inline-block tooltip"
  172. data-tippy-content="MX records invalid"
  173. >
  174. <g fill="none" fill-rule="evenodd">
  175. <circle cx="10" cy="10" r="10" fill="#FF9B9B"></circle>
  176. <polyline
  177. stroke="#AB091E"
  178. stroke-linecap="round"
  179. stroke-linejoin="round"
  180. stroke-width="2"
  181. points="14 6 6 14"
  182. ></polyline>
  183. <polyline
  184. stroke="#AB091E"
  185. stroke-linecap="round"
  186. stroke-linejoin="round"
  187. stroke-width="2"
  188. points="6 6 14 14"
  189. ></polyline>
  190. </g>
  191. </svg>
  192. <svg
  193. v-else
  194. xmlns="http://www.w3.org/2000/svg"
  195. viewBox="0 0 20 20"
  196. class="h-5 w-5 inline-block tooltip"
  197. data-tippy-content="DNS records for sending invalid"
  198. >
  199. <g fill="none" fill-rule="evenodd">
  200. <circle cx="10" cy="10" r="10" fill="#FF9B9B"></circle>
  201. <polyline
  202. stroke="#AB091E"
  203. stroke-linecap="round"
  204. stroke-linejoin="round"
  205. stroke-width="2"
  206. points="14 6 6 14"
  207. ></polyline>
  208. <polyline
  209. stroke="#AB091E"
  210. stroke-linecap="round"
  211. stroke-linejoin="round"
  212. stroke-width="2"
  213. points="6 6 14 14"
  214. ></polyline>
  215. </g>
  216. </svg>
  217. <button
  218. @click="openCheckRecordsModal(rows[props.row.originalIndex])"
  219. class="focus:outline-none text-sm ml-2"
  220. >
  221. Recheck
  222. </button>
  223. </div>
  224. <button
  225. v-else
  226. @click="openCheckRecordsModal(rows[props.row.originalIndex])"
  227. class="focus:outline-none text-sm"
  228. >
  229. Check Records
  230. </button>
  231. </span>
  232. <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
  233. <icon
  234. name="trash"
  235. class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
  236. @click="openDeleteModal(props.row.id)"
  237. />
  238. </span>
  239. </template>
  240. </vue-good-table>
  241. <div v-else class="bg-white rounded shadow overflow-x-auto">
  242. <div class="p-8 text-center text-lg text-grey-700">
  243. <h1 class="mb-6 text-xl text-indigo-800 font-semibold">
  244. This is where you can set up and view custom domains
  245. </h1>
  246. <div class="mx-auto mb-6 w-24 border-b-2 border-grey-200"></div>
  247. <p class="mb-4">
  248. To get started all you have to do is add a TXT record to your domain to verify ownership
  249. and then add the domain here by clicking the button above.
  250. </p>
  251. <p class="mb-4">The TXT record needs to have the following values:</p>
  252. <p class="mb-4">
  253. Type: <b>TXT</b><br />
  254. Host: <b>@</b><br />
  255. Value: <b>aa-verify={{ aaVerify }}</b
  256. ><br />
  257. </p>
  258. <p>
  259. Once the DNS changes propagate and you have verified ownership of the domain you will need
  260. to add a few more records to be able to receive emails at your own domain.
  261. </p>
  262. </div>
  263. </div>
  264. <Modal :open="addDomainModalOpen" @close="closeCheckRecordsModal">
  265. <template v-if="!domainToCheck" v-slot:title> Add new domain </template>
  266. <template v-else v-slot:title> Check DNS records </template>
  267. <template v-if="!domainToCheck" v-slot:content>
  268. <p class="mt-4 mb-2 text-grey-700">
  269. To verify ownership of the domain, please add the following TXT record and then click Add
  270. Domain below.
  271. </p>
  272. <div class="table w-full">
  273. <div class="table-row">
  274. <div class="table-cell py-2 font-semibold">Type</div>
  275. <div class="table-cell p-2 font-semibold">Host</div>
  276. <div class="table-cell py-2 font-semibold">Value/Points to</div>
  277. </div>
  278. <div class="table-row">
  279. <div class="table-cell py-2">TXT</div>
  280. <div class="table-cell p-2">@</div>
  281. <div class="table-cell py-2 break-all">aa-verify={{ aaVerify }}</div>
  282. </div>
  283. </div>
  284. <div class="mt-6">
  285. <p v-show="errors.newDomain" class="mb-3 text-red-500 text-sm">
  286. {{ errors.newDomain }}
  287. </p>
  288. <input
  289. v-model="newDomain"
  290. type="text"
  291. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3 mb-6"
  292. :class="errors.newDomain ? 'border-red-500' : ''"
  293. placeholder="example.com"
  294. autofocus
  295. />
  296. <button
  297. @click="validateNewDomain"
  298. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  299. :class="addDomainLoading ? 'cursor-not-allowed' : ''"
  300. :disabled="addDomainLoading"
  301. >
  302. Add Domain
  303. <loader v-if="addDomainLoading" />
  304. </button>
  305. <button
  306. @click="addDomainModalOpen = false"
  307. 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"
  308. >
  309. Cancel
  310. </button>
  311. </div>
  312. </template>
  313. <template v-else v-slot:content>
  314. <h2
  315. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  316. >
  317. Check DNS records
  318. </h2>
  319. <p class="mt-4 mb-2 text-grey-700">
  320. Please set the following DNS records for your custom domain. If you have more than one MX
  321. record then the MX record below should have the lowest priority (e.g. 10).
  322. </p>
  323. <div class="table w-full">
  324. <div class="table-row">
  325. <div class="table-cell py-2 font-semibold">Type</div>
  326. <div class="table-cell py-2 px-4 font-semibold">Host</div>
  327. <div class="table-cell py-2 font-semibold">Value/Points to</div>
  328. </div>
  329. <div class="table-row">
  330. <div class="table-cell py-2">MX</div>
  331. <div class="table-cell py-2 px-4">@</div>
  332. <div class="table-cell py-2 break-words">{{ hostname }}</div>
  333. </div>
  334. <div class="table-row">
  335. <div class="table-cell py-2">TXT</div>
  336. <div class="table-cell py-2 px-4">@</div>
  337. <div class="table-cell py-2 break-words">v=spf1 mx -all</div>
  338. </div>
  339. <div class="table-row">
  340. <div class="table-cell py-2">CNAME</div>
  341. <div class="table-cell py-2 px-4">default._domainkey</div>
  342. <div class="table-cell py-2 break-words">default._domainkey.{{ domainName }}.</div>
  343. </div>
  344. <div class="table-row">
  345. <div class="table-cell py-2">TXT</div>
  346. <div class="table-cell py-2 px-4">_dmarc</div>
  347. <div class="table-cell py-2 break-words">v=DMARC1; p=quarantine; adkim=s</div>
  348. </div>
  349. </div>
  350. <div class="mt-6">
  351. <button
  352. @click="checkRecords(domainToCheck)"
  353. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  354. :class="checkRecordsLoading ? 'cursor-not-allowed' : ''"
  355. :disabled="checkRecordsLoading"
  356. >
  357. Check Records
  358. <loader v-if="checkRecordsLoading" />
  359. </button>
  360. <button
  361. @click="closeCheckRecordsModal"
  362. 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"
  363. >
  364. Cancel
  365. </button>
  366. </div>
  367. </template>
  368. </Modal>
  369. <Modal :open="domainDefaultRecipientModalOpen" @close="closeDomainDefaultRecipientModal">
  370. <template v-slot:title> Update Default Recipient </template>
  371. <template v-slot:content>
  372. <p class="my-4 text-grey-700">
  373. Select the default recipient for this domain. This overrides the default recipient in your
  374. account settings. Leave it empty if you would like to use the default recipient in your
  375. account settings.
  376. </p>
  377. <multiselect
  378. v-model="defaultRecipientId"
  379. :options="recipientOptions"
  380. mode="single"
  381. value-prop="id"
  382. :close-on-select="true"
  383. :clear-on-select="false"
  384. :searchable="false"
  385. :allow-empty="true"
  386. placeholder="Select recipient"
  387. label="email"
  388. track-by="email"
  389. >
  390. </multiselect>
  391. <div class="mt-6">
  392. <button
  393. type="button"
  394. @click="editDefaultRecipient()"
  395. class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
  396. :class="editDefaultRecipientLoading ? 'cursor-not-allowed' : ''"
  397. :disabled="editDefaultRecipientLoading"
  398. >
  399. Update Default Recipient
  400. <loader v-if="editDefaultRecipientLoading" />
  401. </button>
  402. <button
  403. @click="closeDomainDefaultRecipientModal()"
  404. 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"
  405. >
  406. Cancel
  407. </button>
  408. </div>
  409. </template>
  410. </Modal>
  411. <Modal :open="deleteDomainModalOpen" @close="closeDeleteModal">
  412. <template v-slot:title> Delete domain </template>
  413. <template v-slot:content>
  414. <p class="mt-4 text-grey-700">
  415. Are you sure you want to delete this domain? This will also delete all aliases associated
  416. with this domain. You will no longer be able to receive any emails at this domain.
  417. </p>
  418. <div class="mt-6">
  419. <button
  420. type="button"
  421. @click="deleteDomain(domainIdToDelete)"
  422. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  423. :class="deleteDomainLoading ? 'cursor-not-allowed' : ''"
  424. :disabled="deleteDomainLoading"
  425. >
  426. Delete domain
  427. <loader v-if="deleteDomainLoading" />
  428. </button>
  429. <button
  430. @click="closeDeleteModal"
  431. 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"
  432. >
  433. Cancel
  434. </button>
  435. </div>
  436. </template>
  437. </Modal>
  438. </div>
  439. </template>
  440. <script>
  441. import Modal from './../components/Modal.vue'
  442. import Toggle from './../components/Toggle.vue'
  443. import { roundArrow } from 'tippy.js'
  444. import 'tippy.js/dist/svg-arrow.css'
  445. import 'tippy.js/dist/tippy.css'
  446. import tippy from 'tippy.js'
  447. import Multiselect from '@vueform/multiselect'
  448. export default {
  449. props: {
  450. initialDomains: {
  451. type: Array,
  452. required: true,
  453. },
  454. domainName: {
  455. type: String,
  456. required: true,
  457. },
  458. hostname: {
  459. type: String,
  460. required: true,
  461. },
  462. recipientOptions: {
  463. type: Array,
  464. required: true,
  465. },
  466. aaVerify: {
  467. type: String,
  468. required: true,
  469. },
  470. },
  471. components: {
  472. Modal,
  473. Toggle,
  474. Multiselect,
  475. },
  476. data() {
  477. return {
  478. newDomain: '',
  479. search: '',
  480. addDomainLoading: false,
  481. addDomainModalOpen: false,
  482. domainIdToDelete: null,
  483. domainIdToEdit: null,
  484. domainDescriptionToEdit: '',
  485. domainToCheck: null,
  486. deleteDomainLoading: false,
  487. deleteDomainModalOpen: false,
  488. checkRecordsLoading: false,
  489. domainDefaultRecipientModalOpen: false,
  490. defaultRecipientDomainToEdit: {},
  491. defaultRecipientId: null,
  492. editDefaultRecipientLoading: false,
  493. errors: {},
  494. columns: [
  495. {
  496. label: 'Created',
  497. field: 'created_at',
  498. globalSearchDisabled: true,
  499. },
  500. {
  501. label: 'Domain',
  502. field: 'domain',
  503. },
  504. {
  505. label: 'Description',
  506. field: 'description',
  507. },
  508. {
  509. label: 'Default Recipient',
  510. field: 'default_recipient',
  511. sortable: false,
  512. globalSearchDisabled: true,
  513. },
  514. {
  515. label: 'Alias Count',
  516. field: 'aliases_count',
  517. type: 'number',
  518. globalSearchDisabled: true,
  519. },
  520. {
  521. label: 'Active',
  522. field: 'active',
  523. type: 'boolean',
  524. globalSearchDisabled: true,
  525. },
  526. {
  527. label: 'Catch-All',
  528. field: 'catch_all',
  529. type: 'boolean',
  530. globalSearchDisabled: true,
  531. },
  532. {
  533. label: 'Verified Records',
  534. field: 'domain_sending_verified_at',
  535. globalSearchDisabled: true,
  536. },
  537. {
  538. label: '',
  539. field: 'actions',
  540. sortable: false,
  541. globalSearchDisabled: true,
  542. },
  543. ],
  544. rows: this.initialDomains,
  545. tippyInstance: null,
  546. }
  547. },
  548. watch: {
  549. domainIdToEdit: _.debounce(function () {
  550. this.addTooltips()
  551. }, 50),
  552. },
  553. methods: {
  554. addTooltips() {
  555. if (this.tippyInstance) {
  556. _.each(this.tippyInstance, instance => instance.destroy())
  557. }
  558. this.tippyInstance = tippy('.tooltip', {
  559. arrow: roundArrow,
  560. allowHTML: true,
  561. })
  562. },
  563. debounceToolips: _.debounce(function () {
  564. this.addTooltips()
  565. }, 50),
  566. validateNewDomain(e) {
  567. this.errors = {}
  568. if (!this.newDomain) {
  569. this.errors.newDomain = 'Domain name required'
  570. } else if (!this.validDomain(this.newDomain)) {
  571. this.errors.newDomain = 'Please enter a valid domain name'
  572. }
  573. if (!this.errors.newDomain) {
  574. this.addNewDomain()
  575. }
  576. e.preventDefault()
  577. },
  578. addNewDomain() {
  579. this.addDomainLoading = true
  580. axios
  581. .post(
  582. '/api/v1/domains',
  583. JSON.stringify({
  584. domain: this.newDomain,
  585. }),
  586. {
  587. headers: { 'Content-Type': 'application/json' },
  588. }
  589. )
  590. .then(({ data }) => {
  591. // In order to get new TXT verification value
  592. location.reload()
  593. })
  594. .catch(error => {
  595. this.addDomainLoading = false
  596. if (error.response.status === 422) {
  597. this.error(error.response.data.errors.domain[0])
  598. } else if (error.response.status === 429) {
  599. this.error('You are making too many requests')
  600. } else if (error.response.status === 404) {
  601. this.warn(
  602. 'Verification TXT record not found, this could be due to DNS caching, please try again shortly.'
  603. )
  604. } else {
  605. this.error()
  606. }
  607. })
  608. },
  609. checkRecords(domain) {
  610. this.checkRecordsLoading = true
  611. axios
  612. .get(`/domains/${domain.id}/check-sending`)
  613. .then(({ data }) => {
  614. this.checkRecordsLoading = false
  615. if (data.success === true) {
  616. this.closeCheckRecordsModal()
  617. this.success(data.message)
  618. domain.domain_sending_verified_at = data.data.domain_sending_verified_at
  619. domain.domain_mx_validated_at = data.data.domain_mx_validated_at
  620. } else {
  621. this.warn(data.message)
  622. }
  623. })
  624. .catch(error => {
  625. this.checkRecordsLoading = false
  626. if (error.response.status === 429) {
  627. this.error('Please wait a little while before checking the records again')
  628. } else {
  629. this.error()
  630. }
  631. })
  632. },
  633. openDeleteModal(id) {
  634. this.deleteDomainModalOpen = true
  635. this.domainIdToDelete = id
  636. },
  637. closeDeleteModal() {
  638. this.deleteDomainModalOpen = false
  639. this.domainIdToDelete = null
  640. },
  641. openDomainDefaultRecipientModal(domain) {
  642. this.domainDefaultRecipientModalOpen = true
  643. this.defaultRecipientDomainToEdit = domain
  644. this.defaultRecipientId = domain.default_recipient_id
  645. },
  646. closeDomainDefaultRecipientModal() {
  647. this.domainDefaultRecipientModalOpen = false
  648. this.defaultRecipientDomainToEdit = {}
  649. this.defaultRecipientId = null
  650. },
  651. openCheckRecordsModal(domain) {
  652. this.domainToCheck = domain
  653. this.addDomainModalOpen = true
  654. },
  655. closeCheckRecordsModal() {
  656. this.addDomainModalOpen = false
  657. _.delay(() => (this.domainToCheck = null), 300)
  658. },
  659. editDomain(domain) {
  660. if (this.domainDescriptionToEdit.length > 200) {
  661. return this.error('Description cannot be more than 200 characters')
  662. }
  663. axios
  664. .patch(
  665. `/api/v1/domains/${domain.id}`,
  666. JSON.stringify({
  667. description: this.domainDescriptionToEdit,
  668. }),
  669. {
  670. headers: { 'Content-Type': 'application/json' },
  671. }
  672. )
  673. .then(response => {
  674. domain.description = this.domainDescriptionToEdit
  675. this.domainIdToEdit = null
  676. this.domainDescriptionToEdit = ''
  677. this.success('Domain description updated')
  678. })
  679. .catch(error => {
  680. this.domainIdToEdit = null
  681. this.domainDescriptionToEdit = ''
  682. this.error()
  683. })
  684. },
  685. editDefaultRecipient() {
  686. this.editDefaultRecipientLoading = true
  687. axios
  688. .patch(
  689. `/api/v1/domains/${this.defaultRecipientDomainToEdit.id}/default-recipient`,
  690. JSON.stringify({
  691. default_recipient: this.defaultRecipientId,
  692. }),
  693. {
  694. headers: { 'Content-Type': 'application/json' },
  695. }
  696. )
  697. .then(response => {
  698. let domain = _.find(this.rows, ['id', this.defaultRecipientDomainToEdit.id])
  699. domain.default_recipient = _.find(this.recipientOptions, ['id', this.defaultRecipientId])
  700. domain.default_recipient_id = this.defaultRecipientId
  701. this.domainDefaultRecipientModalOpen = false
  702. this.editDefaultRecipientLoading = false
  703. this.defaultRecipientId = null
  704. this.success("Domain's default recipient updated")
  705. })
  706. .catch(error => {
  707. this.domainDefaultRecipientModalOpen = false
  708. this.editDefaultRecipientLoading = false
  709. this.defaultRecipientId = null
  710. this.error()
  711. })
  712. },
  713. activateDomain(id) {
  714. axios
  715. .post(
  716. `/api/v1/active-domains`,
  717. JSON.stringify({
  718. id: id,
  719. }),
  720. {
  721. headers: { 'Content-Type': 'application/json' },
  722. }
  723. )
  724. .then(response => {
  725. //
  726. })
  727. .catch(error => {
  728. this.error()
  729. })
  730. },
  731. deactivateDomain(id) {
  732. axios
  733. .delete(`/api/v1/active-domains/${id}`)
  734. .then(response => {
  735. //
  736. })
  737. .catch(error => {
  738. this.error()
  739. })
  740. },
  741. enableCatchAll(id) {
  742. axios
  743. .post(
  744. `/api/v1/catch-all-domains`,
  745. JSON.stringify({
  746. id: id,
  747. }),
  748. {
  749. headers: { 'Content-Type': 'application/json' },
  750. }
  751. )
  752. .then(response => {
  753. //
  754. })
  755. .catch(error => {
  756. if (error.response !== undefined) {
  757. this.error(error.response.data)
  758. } else {
  759. this.error()
  760. }
  761. })
  762. },
  763. disableCatchAll(id) {
  764. axios
  765. .delete(`/api/v1/catch-all-domains/${id}`)
  766. .then(response => {
  767. //
  768. })
  769. .catch(error => {
  770. if (error.response !== undefined) {
  771. this.error(error.response.data)
  772. } else {
  773. this.error()
  774. }
  775. })
  776. },
  777. deleteDomain(id) {
  778. this.deleteDomainLoading = true
  779. axios
  780. .delete(`/api/v1/domains/${id}`)
  781. .then(response => {
  782. this.rows = _.reject(this.rows, domain => domain.id === id)
  783. this.deleteDomainModalOpen = false
  784. this.deleteDomainLoading = false
  785. })
  786. .catch(error => {
  787. this.error()
  788. this.deleteDomainLoading = false
  789. this.deleteDomainModalOpen = false
  790. })
  791. },
  792. validDomain(domain) {
  793. let re = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/
  794. return re.test(domain)
  795. },
  796. clipboardSuccess() {
  797. this.success('Copied to clipboard')
  798. },
  799. clipboardError() {
  800. this.error('Could not copy to clipboard')
  801. },
  802. warn(text = '') {
  803. this.$notify({
  804. title: 'Information',
  805. text: text,
  806. type: 'warn',
  807. })
  808. },
  809. success(text = '') {
  810. this.$notify({
  811. title: 'Success',
  812. text: text,
  813. type: 'success',
  814. })
  815. },
  816. error(text = 'An error has occurred, please try again later') {
  817. this.$notify({
  818. title: 'Error',
  819. text: text,
  820. type: 'error',
  821. })
  822. },
  823. },
  824. }
  825. </script>