Domains.vue 25 KB

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