Domains.vue 26 KB

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