Aliases.vue 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347
  1. <template>
  2. <div class="aliases">
  3. <div class="flex flex-wrap flex-row items-center justify-between mb-8 md:px-2 lg:px-6">
  4. <div
  5. class="w-full md:w-1/2 lg:w-1/3 xl:w-1/6 md:-mx-2 lg:-mx-6 rounded overflow-hidden shadow-md bg-white mb-4 lg:mb-4 xl:mb-0"
  6. >
  7. <div class="p-4 flex items-center justify-between relative">
  8. <icon
  9. name="check-circle"
  10. class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
  11. />
  12. <div class="font-bold text-xl md:text-3xl text-indigo-800">
  13. {{ totalActive }}
  14. <p class="text-grey-300 text-sm tracking-wide uppercase">Active</p>
  15. </div>
  16. </div>
  17. </div>
  18. <div
  19. class="w-full md:w-1/2 lg:w-1/3 xl:w-1/6 md:-mx-2 lg:-mx-6 rounded overflow-hidden shadow-md bg-white mb-4 lg:mb-4 xl:mb-0"
  20. >
  21. <div class="p-4 flex items-center justify-between relative">
  22. <icon
  23. name="cross-circle"
  24. class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
  25. />
  26. <div class="font-bold text-xl md:text-3xl text-indigo-800">
  27. {{ totalInactive }}
  28. <p class="text-grey-300 text-sm tracking-wide uppercase">Inactive</p>
  29. </div>
  30. </div>
  31. </div>
  32. <div
  33. class="w-full md:w-1/2 lg:w-1/3 xl:w-1/6 md:-mx-2 lg:-mx-6 rounded overflow-hidden shadow-md bg-white mb-4 lg:mb-4 xl:mb-0"
  34. >
  35. <div class="p-4 flex items-center justify-between relative">
  36. <icon
  37. name="send"
  38. class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
  39. />
  40. <div class="font-bold text-xl md:text-3xl text-indigo-800">
  41. {{ totalForwarded }}
  42. <p class="text-grey-300 text-sm tracking-wide uppercase">Emails Forwarded</p>
  43. </div>
  44. </div>
  45. </div>
  46. <div
  47. class="w-full md:w-1/2 lg:w-1/3 xl:w-1/6 md:-mx-2 lg:-mx-6 rounded overflow-hidden shadow-md bg-white mb-4 lg:mb-0"
  48. >
  49. <div class="p-4 flex items-center justify-between relative">
  50. <icon
  51. name="blocked"
  52. class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
  53. />
  54. <div class="font-bold text-xl md:text-3xl text-indigo-800">
  55. {{ totalBlocked }}
  56. <p class="text-grey-300 text-sm tracking-wide uppercase">Emails Blocked</p>
  57. </div>
  58. </div>
  59. </div>
  60. <div
  61. class="w-full md:w-1/2 lg:w-1/3 xl:w-1/6 md:-mx-2 lg:-mx-6 rounded overflow-hidden shadow-md bg-white mb-4 md:mb-0"
  62. >
  63. <div class="p-4 flex items-center justify-between relative">
  64. <icon
  65. name="corner-up-left"
  66. class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
  67. />
  68. <div class="font-bold text-xl md:text-3xl text-indigo-800">
  69. {{ totalReplies }}
  70. <p class="text-grey-300 text-sm tracking-wide uppercase">Email Replies</p>
  71. </div>
  72. </div>
  73. </div>
  74. <div
  75. class="w-full md:w-1/2 lg:w-1/3 xl:w-1/6 md:-mx-2 lg:-mx-6 rounded overflow-hidden shadow-md bg-white"
  76. >
  77. <div class="p-4 flex items-center justify-between relative">
  78. <icon
  79. name="inbox"
  80. class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
  81. />
  82. <div class="font-bold text-xl md:text-3xl text-indigo-800">
  83. {{ bandwidthMb }}<span class="text-sm tracking-wide uppercase">MB</span>
  84. <p class="text-grey-300 text-sm tracking-wide uppercase">Bandwidth ({{ month }})</p>
  85. </div>
  86. </div>
  87. </div>
  88. </div>
  89. <div class="mb-6 flex flex-col md:flex-row justify-between md:items-center">
  90. <div class="relative">
  91. <input
  92. v-model="search"
  93. @keyup.esc="search = ''"
  94. tabindex="0"
  95. type="text"
  96. class="w-full md:w-64 appearance-none shadow bg-white text-grey-700 focus:outline-none rounded py-3 pl-3 pr-8"
  97. placeholder="Search Aliases"
  98. />
  99. <icon
  100. v-if="search"
  101. @click="search = ''"
  102. name="close-circle"
  103. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
  104. />
  105. <icon
  106. v-else
  107. name="search"
  108. class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current pointer-events-none mr-2 flex items-center"
  109. />
  110. </div>
  111. <div class="flex flex-wrap mt-4 md:mt-0">
  112. <div class="block relative mr-4">
  113. <select
  114. v-model="showAliases"
  115. class="block appearance-none w-full text-grey-700 bg-white p-3 pr-8 rounded shadow focus:ring"
  116. required
  117. >
  118. <option value="without">Hide Deleted</option>
  119. <option value="with">Show Deleted</option>
  120. <option value="only">Deleted Only</option>
  121. </select>
  122. <div
  123. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  124. >
  125. <svg
  126. class="fill-current h-4 w-4"
  127. xmlns="http://www.w3.org/2000/svg"
  128. viewBox="0 0 20 20"
  129. >
  130. <path
  131. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  132. />
  133. </svg>
  134. </div>
  135. </div>
  136. <div>
  137. <button
  138. @click="generateAliasModalOpen = true"
  139. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none ml-auto"
  140. >
  141. Create New Alias
  142. </button>
  143. </div>
  144. </div>
  145. </div>
  146. <vue-good-table
  147. v-if="initialAliases.length"
  148. v-on:search="debounceToolips"
  149. v-on:page-change="debounceToolips"
  150. v-on:per-page-change="debounceToolips"
  151. :columns="columns"
  152. :rows="rows"
  153. :search-options="{
  154. enabled: true,
  155. skipDiacritics: true,
  156. externalQuery: search,
  157. }"
  158. :sort-options="{
  159. enabled: true,
  160. initialSortBy: { field: 'created_at', type: 'desc' },
  161. }"
  162. :pagination-options="{
  163. enabled: true,
  164. mode: 'pages',
  165. perPage: 25,
  166. perPageDropdown: [25, 50, 100],
  167. rowsPerPageLabel: 'Aliases per page',
  168. }"
  169. styleClass="vgt-table"
  170. >
  171. <template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
  172. No aliases found for that search!
  173. </template>
  174. <template #table-row="props">
  175. <span v-if="props.column.field == 'created_at'" class="flex items-center">
  176. <span
  177. :class="`bg-${getAliasStatus(props.row).colour}-100`"
  178. class="tooltip outline-none h-4 w-4 rounded-full flex items-center justify-center mr-2"
  179. :data-tippy-content="getAliasStatus(props.row).status"
  180. tabindex="-1"
  181. >
  182. <span
  183. :class="`bg-${getAliasStatus(props.row).colour}-400`"
  184. class="h-2 w-2 rounded-full"
  185. ></span>
  186. </span>
  187. <span
  188. class="tooltip outline-none text-sm whitespace-nowrap"
  189. :data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
  190. >{{ $filters.timeAgo(props.row.created_at) }}
  191. </span>
  192. </span>
  193. <span v-else-if="props.column.field == 'email'" class="block">
  194. <span
  195. class="text-grey-400 tooltip cursor-pointer outline-none"
  196. data-tippy-content="Click to copy"
  197. v-clipboard="() => getAliasEmail(rows[props.row.originalIndex])"
  198. v-clipboard:success="clipboardSuccess"
  199. v-clipboard:error="clipboardError"
  200. ><span class="font-semibold text-indigo-800">{{
  201. $filters.truncate(getAliasLocalPart(props.row), 60)
  202. }}</span
  203. ><span v-if="getAliasLocalPart(props.row).length <= 60">{{
  204. $filters.truncate('@' + props.row.domain, 60 - getAliasLocalPart(props.row).length)
  205. }}</span>
  206. </span>
  207. <div v-if="aliasIdToEdit === props.row.id" class="flex items-center">
  208. <input
  209. @keyup.enter="editAlias(rows[props.row.originalIndex])"
  210. @keyup.esc="aliasIdToEdit = aliasDescriptionToEdit = ''"
  211. v-model="aliasDescriptionToEdit"
  212. type="text"
  213. class="grow text-sm appearance-none bg-grey-100 border text-grey-700 focus:outline-none rounded px-2 py-1"
  214. :class="aliasDescriptionToEdit.length > 200 ? 'border-red-500' : 'border-transparent'"
  215. placeholder="Add description"
  216. tabindex="0"
  217. autofocus
  218. />
  219. <icon
  220. name="close"
  221. class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
  222. @click="aliasIdToEdit = aliasDescriptionToEdit = ''"
  223. />
  224. <icon
  225. name="save"
  226. class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
  227. @click="editAlias(rows[props.row.originalIndex])"
  228. />
  229. </div>
  230. <div v-else-if="props.row.description" class="flex items-center">
  231. <span class="inline-block text-grey-400 text-sm py-1 border border-transparent">
  232. {{ $filters.truncate(props.row.description, 60) }}
  233. </span>
  234. <icon
  235. name="edit"
  236. class="inline-block w-6 h-6 ml-2 text-grey-300 fill-current cursor-pointer"
  237. @click="
  238. ;(aliasIdToEdit = props.row.id), (aliasDescriptionToEdit = props.row.description)
  239. "
  240. />
  241. </div>
  242. <div v-else>
  243. <span
  244. class="inline-block text-grey-300 text-sm cursor-pointer py-1 border border-transparent"
  245. @click=";(aliasIdToEdit = props.row.id), (aliasDescriptionToEdit = '')"
  246. >Add description</span
  247. >
  248. </div>
  249. </span>
  250. <span
  251. v-else-if="props.column.field == 'recipients'"
  252. class="flex items-center justify-center"
  253. >
  254. <span
  255. v-if="props.row.recipients.length && props.row.id !== recipientsAliasToEdit.id"
  256. class="inline-block tooltip outline-none font-semibold text-indigo-800"
  257. :data-tippy-content="recipientsTooltip(props.row.recipients)"
  258. >
  259. {{ props.row.recipients.length }}
  260. </span>
  261. <span v-else-if="props.row.id === recipientsAliasToEdit.id">{{
  262. aliasRecipientsToEdit.length ? aliasRecipientsToEdit.length : '1'
  263. }}</span>
  264. <span
  265. v-else-if="has(props.row.aliasable, 'default_recipient.email')"
  266. class="py-1 px-2 text-xs bg-yellow-200 text-yellow-900 rounded-full tooltip outline-none"
  267. :data-tippy-content="props.row.aliasable.default_recipient.email"
  268. >{{
  269. props.row.aliasable_type === 'App\\Models\\Domain' ? 'domain' : 'username'
  270. }}'s</span
  271. >
  272. <span
  273. v-else
  274. class="py-1 px-2 text-xs bg-yellow-200 text-yellow-900 rounded-full tooltip outline-none"
  275. :data-tippy-content="defaultRecipientEmail"
  276. >default</span
  277. >
  278. <icon
  279. name="edit"
  280. class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
  281. @click="openAliasRecipientsModal(props.row)"
  282. />
  283. </span>
  284. <span
  285. v-else-if="props.column.field == 'emails_forwarded'"
  286. class="font-semibold text-indigo-800"
  287. >
  288. {{ props.row.emails_forwarded }}
  289. </span>
  290. <span
  291. v-else-if="props.column.field == 'emails_blocked'"
  292. class="font-semibold text-indigo-800"
  293. >
  294. {{ props.row.emails_blocked }}
  295. </span>
  296. <span
  297. v-else-if="props.column.field == 'emails_replied'"
  298. class="font-semibold text-indigo-800"
  299. >
  300. {{ props.row.emails_replied }} <span class="text-grey-300">/</span>
  301. {{ props.row.emails_sent }}
  302. </span>
  303. <span v-else-if="props.column.field === 'active'" class="flex items-center">
  304. <Toggle
  305. v-model="rows[props.row.originalIndex].active"
  306. @on="activateAlias(rows[props.row.originalIndex])"
  307. @off="deactivateAlias(rows[props.row.originalIndex])"
  308. />
  309. </span>
  310. <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
  311. <more-options>
  312. <div role="none">
  313. <MenuItem>
  314. <span
  315. @click="openSendFromModal(props.row)"
  316. class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
  317. role="menuitem"
  318. >
  319. <icon name="send" class="block mr-3 w-5 h-5 text-grey-300 outline-none" />
  320. Send From
  321. </span>
  322. </MenuItem>
  323. </div>
  324. <MenuItem v-if="props.row.deleted_at">
  325. <span
  326. @click="openRestoreModal(props.row.id)"
  327. class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
  328. role="menuitem"
  329. >
  330. <icon
  331. name="undo"
  332. class="block mr-3 w-5 h-5 text-grey-300 fill-current outline-none"
  333. />
  334. Restore
  335. </span>
  336. </MenuItem>
  337. <MenuItem v-else>
  338. <span
  339. @click="openDeleteModal(props.row)"
  340. class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
  341. role="menuitem"
  342. >
  343. <icon
  344. name="trash"
  345. class="block mr-3 w-5 h-5 text-grey-300 fill-current outline-none"
  346. />
  347. Delete
  348. </span>
  349. </MenuItem>
  350. <MenuItem>
  351. <span
  352. @click="openForgetModal(props.row)"
  353. class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
  354. role="menuitem"
  355. >
  356. <icon
  357. name="rubber"
  358. class="block mr-3 w-5 h-5 text-grey-300 fill-current outline-none"
  359. />
  360. Forget
  361. </span>
  362. </MenuItem>
  363. </more-options>
  364. </span>
  365. </template>
  366. </vue-good-table>
  367. <div v-else class="bg-white rounded shadow overflow-x-auto">
  368. <div class="p-8 text-center text-lg text-grey-700">
  369. <h1 class="mb-6 text-2xl text-indigo-800 font-semibold">
  370. It doesn't look like you have any aliases yet!
  371. </h1>
  372. <div class="mx-auto mb-6 w-24 border-b-2 border-grey-200"></div>
  373. <p class="mb-4">There are two ways to create new aliases.</p>
  374. <h3 class="mb-4 text-xl text-indigo-800 font-semibold">
  375. Option 1: Create aliases on the fly
  376. </h3>
  377. <p class="mb-4">
  378. To create aliases on the fly all you have to do is make up any new alias and give that out
  379. instead of your real email address.
  380. </p>
  381. <p class="mb-4">
  382. Let's say you're signing up to <b>example.com</b> you could enter
  383. <b>example@{{ subdomain }}</b> as your email address.
  384. </p>
  385. <p class="mb-4">
  386. The alias will show up here automatically as soon as it has forwarded its first email.
  387. </p>
  388. <p class="mb-4">
  389. If you start receiving spam to the alias you can simply deactivate it or delete it all
  390. together!
  391. </p>
  392. <p class="mb-4">
  393. Try it out now by sending an email to <b>first@{{ subdomain }}</b> and then refresh this
  394. page.
  395. </p>
  396. <h3 class="mb-4 text-xl text-indigo-800 font-semibold">
  397. Option 2: Generate a unique random alias
  398. </h3>
  399. <p class="mb-4">
  400. You can click the button above to generate a random alias that will look something like
  401. this:
  402. </p>
  403. <p class="mb-4">
  404. <b>x481n904@{{ domain }}</b>
  405. </p>
  406. <p>
  407. Useful if you do not wish to include your username in the email as a potential link
  408. between aliases.
  409. </p>
  410. </div>
  411. </div>
  412. <Modal :open="generateAliasModalOpen" @close="generateAliasModalOpen = false">
  413. <template v-slot:title> Create new alias </template>
  414. <template v-slot:content>
  415. <p class="mt-4 text-grey-700">
  416. Other aliases e.g. alias@{{ subdomain }} can also be created automatically when they
  417. receive their first email.
  418. </p>
  419. <label for="alias_domain" class="block text-grey-700 text-sm my-2"> Alias Domain: </label>
  420. <div class="block relative w-full mb-4">
  421. <select
  422. v-model="generateAliasDomain"
  423. id="alias_domain"
  424. class="block appearance-none w-full text-grey-700 bg-grey-100 p-3 pr-8 rounded shadow focus:ring"
  425. required
  426. >
  427. <option v-for="domainOption in domainOptions" :key="domainOption" :value="domainOption">
  428. {{ domainOption }}
  429. </option>
  430. </select>
  431. <div
  432. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  433. >
  434. <svg
  435. class="fill-current h-4 w-4"
  436. xmlns="http://www.w3.org/2000/svg"
  437. viewBox="0 0 20 20"
  438. >
  439. <path
  440. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  441. />
  442. </svg>
  443. </div>
  444. </div>
  445. <label for="alias_format" class="block text-grey-700 text-sm mt-4 mb-2">
  446. Alias Format:
  447. </label>
  448. <div class="block relative w-full mb-4">
  449. <select
  450. v-model="generateAliasFormat"
  451. id="alias_format"
  452. class="block appearance-none w-full text-grey-700 bg-grey-100 p-3 pr-8 rounded shadow focus:ring"
  453. required
  454. >
  455. <option
  456. v-for="formatOption in aliasFormatOptions"
  457. :key="formatOption.value"
  458. :value="formatOption.value"
  459. >
  460. {{ formatOption.label }}
  461. </option>
  462. </select>
  463. <div
  464. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  465. >
  466. <svg
  467. class="fill-current h-4 w-4"
  468. xmlns="http://www.w3.org/2000/svg"
  469. viewBox="0 0 20 20"
  470. >
  471. <path
  472. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  473. />
  474. </svg>
  475. </div>
  476. </div>
  477. <div v-if="generateAliasFormat === 'custom'">
  478. <label for="alias_local_part" class="block text-grey-700 text-sm my-2">
  479. Alias Local Part:
  480. </label>
  481. <p v-show="errors.generateAliasLocalPart" class="mb-3 text-red-500 text-sm">
  482. {{ errors.generateAliasLocalPart }}
  483. </p>
  484. <input
  485. v-model="generateAliasLocalPart"
  486. id="alias_local_part"
  487. type="text"
  488. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3"
  489. :class="errors.generateAliasLocalPart ? 'border-red-500' : ''"
  490. placeholder="Enter local part..."
  491. autofocus
  492. />
  493. </div>
  494. <label for="alias_description" class="block text-grey-700 text-sm my-2">
  495. Description:
  496. </label>
  497. <p v-show="errors.generateAliasDescription" class="mb-3 text-red-500 text-sm">
  498. {{ errors.generateAliasDescription }}
  499. </p>
  500. <input
  501. v-model="generateAliasDescription"
  502. id="alias_description"
  503. type="text"
  504. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3"
  505. :class="errors.generateAliasDescription ? 'border-red-500' : ''"
  506. placeholder="Enter description (optional)..."
  507. autofocus
  508. />
  509. <label for="alias_recipient_ids" class="block text-grey-700 text-sm my-2">
  510. Recipients:
  511. </label>
  512. <p v-show="errors.generateAliasRecipientIds" class="mb-3 text-red-500 text-sm">
  513. {{ errors.generateAliasRecipientIds }}
  514. </p>
  515. <multiselect
  516. id="alias_recipient_ids"
  517. v-model="generateAliasRecipientIds"
  518. mode="tags"
  519. value-prop="id"
  520. :options="recipientOptions"
  521. :multiple="true"
  522. :close-on-select="true"
  523. :clear-on-select="false"
  524. :searchable="true"
  525. :max="10"
  526. placeholder="Select recipient(s) (optional)..."
  527. label="email"
  528. track-by="email"
  529. >
  530. </multiselect>
  531. <div class="mt-6">
  532. <button
  533. @click="generateNewAlias"
  534. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  535. :class="generateAliasLoading ? 'cursor-not-allowed' : ''"
  536. :disabled="generateAliasLoading"
  537. >
  538. Create Alias
  539. <loader v-if="generateAliasLoading" />
  540. </button>
  541. <button
  542. @click="generateAliasModalOpen = false"
  543. 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"
  544. >
  545. Cancel
  546. </button>
  547. </div>
  548. </template>
  549. </Modal>
  550. <Modal :open="editAliasRecipientsModalOpen" @close="closeAliasRecipientsModal">
  551. <template v-slot:title> Update Alias Recipients </template>
  552. <template v-slot:content>
  553. <p class="my-4 text-grey-700">
  554. Select the recipients for this alias. You can choose multiple recipients. Leave it empty
  555. if you would like to use the default recipient.
  556. </p>
  557. <multiselect
  558. v-model="aliasRecipientsToEdit"
  559. mode="tags"
  560. value-prop="id"
  561. :options="recipientOptions"
  562. :multiple="true"
  563. :close-on-select="true"
  564. :clear-on-select="false"
  565. :searchable="true"
  566. :max="10"
  567. placeholder="Select recipient(s)"
  568. label="email"
  569. track-by="email"
  570. >
  571. </multiselect>
  572. <div class="mt-6">
  573. <button
  574. type="button"
  575. @click="editAliasRecipients()"
  576. class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
  577. :class="editAliasRecipientsLoading ? 'cursor-not-allowed' : ''"
  578. :disabled="editAliasRecipientsLoading"
  579. >
  580. Update Recipients
  581. <loader v-if="editAliasRecipientsLoading" />
  582. </button>
  583. <button
  584. @click="closeAliasRecipientsModal()"
  585. 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"
  586. >
  587. Cancel
  588. </button>
  589. </div>
  590. </template>
  591. </Modal>
  592. <Modal :open="restoreAliasModalOpen" @close="closeRestoreModal">
  593. <template v-slot:title> Restore alias </template>
  594. <template v-slot:content>
  595. <p class="mt-4 text-grey-700">
  596. Are you sure you want to restore this alias? Once restored it will be
  597. <b>able to receive emails again</b>.
  598. </p>
  599. <div class="mt-6">
  600. <button
  601. type="button"
  602. @click="restoreAlias(aliasIdToRestore)"
  603. class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
  604. :class="restoreAliasLoading ? 'cursor-not-allowed' : ''"
  605. :disabled="restoreAliasLoading"
  606. >
  607. Restore alias
  608. <loader v-if="restoreAliasLoading" />
  609. </button>
  610. <button
  611. @click="closeRestoreModal"
  612. 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"
  613. >
  614. Cancel
  615. </button>
  616. </div>
  617. </template>
  618. </Modal>
  619. <Modal :open="deleteAliasModalOpen" @close="closeDeleteModal">
  620. <template v-slot:title> Delete alias </template>
  621. <template v-slot:content>
  622. <p class="mt-4 text-grey-700">
  623. Are you sure you want to delete <b class="break-words">{{ aliasToDelete.email }}</b
  624. >? You can restore it if you later change your mind. Once deleted,
  625. <b class="break-words">{{ aliasToDelete.email }}</b> will
  626. <b>reject any emails sent to it</b>.
  627. </p>
  628. <div class="mt-6">
  629. <button
  630. type="button"
  631. @click="deleteAlias(aliasToDelete.id)"
  632. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  633. :class="deleteAliasLoading ? 'cursor-not-allowed' : ''"
  634. :disabled="deleteAliasLoading"
  635. >
  636. Delete alias
  637. <loader v-if="deleteAliasLoading" />
  638. </button>
  639. <button
  640. @click="closeDeleteModal"
  641. 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"
  642. >
  643. Cancel
  644. </button>
  645. </div>
  646. </template>
  647. </Modal>
  648. <Modal :open="forgetAliasModalOpen" @close="closeForgetModal">
  649. <template v-slot:title> Forget alias </template>
  650. <template v-slot:content>
  651. <p class="mt-4 text-grey-700">
  652. Are you sure you want to forget <b class="break-words">{{ aliasToForget.email }}</b
  653. >? Forgetting an alias will disassociate it from your account.
  654. </p>
  655. <p class="mt-4 text-grey-700">
  656. <b>Note:</b> If this alias uses a shared domain then it can <b>never be restored</b> or
  657. used again so make sure you are certain. If it is a standard alias then it can be created
  658. again since it will be as if it never existed.
  659. </p>
  660. <div class="mt-6">
  661. <button
  662. type="button"
  663. @click="forgetAlias(aliasToForget.id)"
  664. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  665. :class="forgetAliasLoading ? 'cursor-not-allowed' : ''"
  666. :disabled="forgetAliasLoading"
  667. >
  668. Forget alias
  669. <loader v-if="forgetAliasLoading" />
  670. </button>
  671. <button
  672. @click="closeForgetModal"
  673. 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"
  674. >
  675. Cancel
  676. </button>
  677. </div>
  678. </template>
  679. </Modal>
  680. <Modal :open="sendFromAliasModalOpen" @close="closeSendFromModal">
  681. <template v-slot:title> Send from alias </template>
  682. <template v-slot:content>
  683. <p class="mt-4 text-grey-700">
  684. Use this to automatically create the correct address to send an email to in order to send
  685. an <b>email from this alias</b>.
  686. </p>
  687. <label for="send_from_alias" class="block text-grey-700 text-sm my-2"> Alias: </label>
  688. <input
  689. v-model="aliasToSendFrom.email"
  690. id="send_from_alias"
  691. type="text"
  692. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3"
  693. disabled
  694. />
  695. <label for="send_from_alias_destination" class="block text-grey-700 text-sm my-2">
  696. Email destination:
  697. </label>
  698. <p v-show="errors.sendFromAliasDestination" class="mb-3 text-red-500 text-sm">
  699. {{ errors.sendFromAliasDestination }}
  700. </p>
  701. <input
  702. v-model="sendFromAliasDestination"
  703. id="send_from_alias_destination"
  704. type="text"
  705. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3"
  706. :class="errors.sendFromAliasDestination ? 'border-red-500' : ''"
  707. placeholder="Enter email..."
  708. autofocus
  709. />
  710. <div v-if="sendFromAliasEmailToSendTo">
  711. <p for="alias_domain" class="block text-grey-700 text-sm my-2">
  712. Send your message to this email:
  713. </p>
  714. <div
  715. v-clipboard="() => sendFromAliasEmailToSendTo"
  716. v-clipboard:success="setSendFromAliasCopied"
  717. class="flex items-center justify-between cursor-pointer text-xs border-t-4 rounded-sm text-green-800 border-green-600 bg-green-100 p-2 mb-3"
  718. role="alert"
  719. >
  720. <span>
  721. {{ sendFromAliasEmailToSendTo }}
  722. </span>
  723. <svg
  724. v-if="sendFromAliasCopied"
  725. viewBox="0 0 24 24"
  726. width="20"
  727. height="20"
  728. stroke="currentColor"
  729. stroke-width="2"
  730. fill="none"
  731. stroke-linecap="round"
  732. stroke-linejoin="round"
  733. >
  734. <polyline points="9 11 12 14 22 4"></polyline>
  735. <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
  736. </svg>
  737. <svg
  738. v-else
  739. viewBox="0 0 24 24"
  740. width="20"
  741. height="20"
  742. stroke="currentColor"
  743. stroke-width="2"
  744. fill="none"
  745. stroke-linecap="round"
  746. stroke-linejoin="round"
  747. >
  748. <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
  749. <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
  750. </svg>
  751. </div>
  752. <a
  753. :href="'mailto:' + sendFromAliasEmailToSendTo"
  754. class="flex items-center justify-between cursor-pointer text-sm border-t-4 rounded-sm text-green-800 border-green-600 bg-green-100 p-2 mb-4"
  755. role="alert"
  756. title="Click To Open Mail Application"
  757. >
  758. Click to open mail application
  759. </a>
  760. </div>
  761. <div class="mt-6">
  762. <button
  763. type="button"
  764. @click="displaySendFromAddress(aliasToSendFrom)"
  765. class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
  766. :class="sendFromAliasLoading ? 'cursor-not-allowed' : ''"
  767. :disabled="sendFromAliasLoading"
  768. >
  769. Show address
  770. <loader v-if="sendFromAliasLoading" />
  771. </button>
  772. <button
  773. @click="closeSendFromModal"
  774. 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"
  775. >
  776. Close
  777. </button>
  778. </div>
  779. </template>
  780. </Modal>
  781. </div>
  782. </template>
  783. <script>
  784. import Modal from './../components/Modal.vue'
  785. import Toggle from './../components/Toggle.vue'
  786. import MoreOptions from './../components/MoreOptions.vue'
  787. import { roundArrow } from 'tippy.js'
  788. import 'tippy.js/dist/svg-arrow.css'
  789. import 'tippy.js/dist/tippy.css'
  790. import tippy from 'tippy.js'
  791. import Multiselect from '@vueform/multiselect'
  792. import { MenuItem } from '@headlessui/vue'
  793. export default {
  794. props: {
  795. defaultRecipientEmail: {
  796. type: String,
  797. required: true,
  798. },
  799. initialAliases: {
  800. type: Array,
  801. required: true,
  802. },
  803. recipientOptions: {
  804. type: Array,
  805. required: true,
  806. },
  807. totalForwarded: {
  808. type: Number,
  809. required: true,
  810. },
  811. totalBlocked: {
  812. type: Number,
  813. required: true,
  814. },
  815. totalReplies: {
  816. type: Number,
  817. required: true,
  818. },
  819. domain: {
  820. type: String,
  821. required: true,
  822. },
  823. subdomain: {
  824. type: String,
  825. required: true,
  826. },
  827. bandwidthMb: {
  828. type: Number,
  829. required: true,
  830. },
  831. month: {
  832. type: String,
  833. required: true,
  834. },
  835. domainOptions: {
  836. type: Array,
  837. required: true,
  838. },
  839. defaultAliasDomain: {
  840. type: String,
  841. required: true,
  842. },
  843. defaultAliasFormat: {
  844. type: String,
  845. required: true,
  846. },
  847. },
  848. components: {
  849. Modal,
  850. Toggle,
  851. Multiselect,
  852. MoreOptions,
  853. MenuItem,
  854. },
  855. data() {
  856. return {
  857. search: '',
  858. showAliases: 'without',
  859. aliasIdToEdit: '',
  860. aliasDescriptionToEdit: '',
  861. aliasToDelete: {},
  862. aliasToForget: {},
  863. aliasToSendFrom: {},
  864. sendFromAliasDestination: '',
  865. sendFromAliasEmailToSendTo: '',
  866. sendFromAliasCopied: false,
  867. aliasIdToRestore: '',
  868. deleteAliasLoading: false,
  869. forgetAliasLoading: false,
  870. deleteAliasModalOpen: false,
  871. forgetAliasModalOpen: false,
  872. sendFromAliasLoading: false,
  873. sendFromAliasModalOpen: false,
  874. restoreAliasLoading: false,
  875. restoreAliasModalOpen: false,
  876. editAliasRecipientsLoading: false,
  877. editAliasRecipientsModalOpen: false,
  878. generateAliasModalOpen: false,
  879. generateAliasLoading: false,
  880. generateAliasDomain: this.defaultAliasDomain ? this.defaultAliasDomain : this.domain,
  881. generateAliasLocalPart: '',
  882. generateAliasDescription: '',
  883. generateAliasRecipientIds: [],
  884. generateAliasFormat: this.defaultAliasFormat ? this.defaultAliasFormat : 'random_characters',
  885. aliasFormatOptions: [
  886. {
  887. value: 'random_characters',
  888. label: 'Random Characters',
  889. },
  890. {
  891. value: 'uuid',
  892. label: 'UUID',
  893. },
  894. {
  895. value: 'random_words',
  896. label: 'Random Words',
  897. },
  898. {
  899. value: 'custom',
  900. label: 'Custom',
  901. },
  902. ],
  903. recipientsAliasToEdit: {},
  904. aliasRecipientsToEdit: [],
  905. columns: [
  906. {
  907. label: 'Created',
  908. field: 'created_at',
  909. globalSearchDisabled: true,
  910. },
  911. {
  912. label: 'Alias',
  913. field: 'email',
  914. },
  915. {
  916. label: 'Recipients',
  917. field: 'recipients',
  918. tdClass: 'text-center',
  919. sortable: true,
  920. sortFn: this.sortRecipients,
  921. globalSearchDisabled: true,
  922. },
  923. {
  924. label: 'Description',
  925. field: 'description',
  926. sortable: false,
  927. hidden: true,
  928. },
  929. {
  930. label: 'Forwarded',
  931. field: 'emails_forwarded',
  932. type: 'number',
  933. tdClass: 'text-center',
  934. globalSearchDisabled: true,
  935. },
  936. {
  937. label: 'Blocked',
  938. field: 'emails_blocked',
  939. type: 'number',
  940. tdClass: 'text-center',
  941. globalSearchDisabled: true,
  942. },
  943. {
  944. label: 'Replies/Sent',
  945. field: 'emails_replied',
  946. type: 'number',
  947. tdClass: 'text-center',
  948. globalSearchDisabled: true,
  949. },
  950. {
  951. label: 'Active',
  952. field: 'active',
  953. type: 'boolean',
  954. globalSearchDisabled: true,
  955. },
  956. {
  957. label: '',
  958. field: 'actions',
  959. sortable: false,
  960. globalSearchDisabled: true,
  961. },
  962. ],
  963. rows: this.initialAliases,
  964. tippyInstance: null,
  965. errors: {},
  966. }
  967. },
  968. watch: {
  969. showAliases() {
  970. this.updateAliases()
  971. },
  972. },
  973. computed: {
  974. activeUuidAliases() {
  975. return _.filter(this.rows, alias => alias.id === alias.local_part && alias.active)
  976. },
  977. totalActive() {
  978. return _.filter(this.rows, 'active').length
  979. },
  980. totalInactive() {
  981. return _.reject(this.rows, 'active').length
  982. },
  983. },
  984. methods: {
  985. addTooltips() {
  986. if (this.tippyInstance) {
  987. _.each(this.tippyInstance, instance => instance.destroy())
  988. }
  989. this.tippyInstance = tippy('.tooltip', {
  990. arrow: roundArrow,
  991. allowHTML: true,
  992. })
  993. },
  994. debounceToolips: _.debounce(function () {
  995. this.addTooltips()
  996. }, 50),
  997. recipientsTooltip(recipients) {
  998. return _.reduce(recipients, (list, recipient) => list + `${recipient.email}<br>`, '')
  999. },
  1000. openDeleteModal(alias) {
  1001. this.deleteAliasModalOpen = true
  1002. this.aliasToDelete = alias
  1003. },
  1004. closeDeleteModal() {
  1005. this.deleteAliasModalOpen = false
  1006. this.aliasToDelete = {}
  1007. },
  1008. openForgetModal(alias) {
  1009. this.forgetAliasModalOpen = true
  1010. this.aliasToForget = alias
  1011. },
  1012. closeForgetModal() {
  1013. this.forgetAliasModalOpen = false
  1014. this.aliasToForget = {}
  1015. },
  1016. openSendFromModal(alias) {
  1017. this.sendFromAliasDestination = ''
  1018. this.sendFromAliasEmailToSendTo = ''
  1019. this.sendFromAliasCopied = false
  1020. this.sendFromAliasModalOpen = true
  1021. this.aliasToSendFrom = alias
  1022. },
  1023. closeSendFromModal() {
  1024. this.sendFromAliasModalOpen = false
  1025. this.aliasToSendFrom = {}
  1026. },
  1027. openRestoreModal(id) {
  1028. this.restoreAliasModalOpen = true
  1029. this.aliasIdToRestore = id
  1030. },
  1031. closeRestoreModal() {
  1032. this.restoreAliasModalOpen = false
  1033. this.aliasIdToRestore = ''
  1034. },
  1035. updateAliases() {
  1036. axios
  1037. .get(`/api/v1/aliases?deleted=${this.showAliases}`, {
  1038. headers: { 'Content-Type': 'application/json' },
  1039. })
  1040. .then(response => {
  1041. this.rows = response.data.data
  1042. })
  1043. .catch(error => {
  1044. this.error()
  1045. })
  1046. },
  1047. deleteAlias(id) {
  1048. this.deleteAliasLoading = true
  1049. axios
  1050. .delete(`/api/v1/aliases/${id}`)
  1051. .then(response => {
  1052. this.rows = _.reject(this.rows, alias => alias.id === id)
  1053. this.deleteAliasModalOpen = false
  1054. this.deleteAliasLoading = false
  1055. })
  1056. .catch(error => {
  1057. this.error()
  1058. this.deleteAliasModalOpen = false
  1059. this.deleteAliasLoading = false
  1060. })
  1061. },
  1062. forgetAlias(id) {
  1063. this.forgetAliasLoading = true
  1064. axios
  1065. .delete(`/api/v1/aliases/${id}/forget`)
  1066. .then(response => {
  1067. this.rows = _.reject(this.rows, alias => alias.id === id)
  1068. this.forgetAliasModalOpen = false
  1069. this.forgetAliasLoading = false
  1070. })
  1071. .catch(error => {
  1072. this.error()
  1073. this.forgetAliasModalOpen = false
  1074. this.forgetAliasLoading = false
  1075. })
  1076. },
  1077. restoreAlias(id) {
  1078. this.restoreAliasLoading = true
  1079. axios
  1080. .patch(`/api/v1/aliases/${id}/restore`, {
  1081. headers: { 'Content-Type': 'application/json' },
  1082. })
  1083. .then(response => {
  1084. this.updateAliases()
  1085. this.restoreAliasModalOpen = false
  1086. this.restoreAliasLoading = false
  1087. this.success('Alias restored successfully')
  1088. })
  1089. .catch(error => {
  1090. this.error()
  1091. this.restoreAliasModalOpen = false
  1092. this.restoreAliasLoading = false
  1093. })
  1094. },
  1095. openAliasRecipientsModal(alias) {
  1096. this.editAliasRecipientsModalOpen = true
  1097. this.recipientsAliasToEdit = alias
  1098. this.aliasRecipientsToEdit = _.map(alias.recipients, recipient => recipient.id)
  1099. },
  1100. closeAliasRecipientsModal() {
  1101. this.editAliasRecipientsModalOpen = false
  1102. this.recipientsAliasToEdit = {}
  1103. this.aliasRecipientsToEdit = []
  1104. },
  1105. editAliasRecipients() {
  1106. this.editAliasRecipientsLoading = true
  1107. axios
  1108. .post(
  1109. '/api/v1/alias-recipients',
  1110. JSON.stringify({
  1111. alias_id: this.recipientsAliasToEdit.id,
  1112. recipient_ids: this.aliasRecipientsToEdit,
  1113. }),
  1114. {
  1115. headers: { 'Content-Type': 'application/json' },
  1116. }
  1117. )
  1118. .then(response => {
  1119. let alias = _.find(this.rows, ['id', this.recipientsAliasToEdit.id])
  1120. alias.recipients = _.filter(this.recipientOptions, recipient =>
  1121. this.aliasRecipientsToEdit.includes(recipient.id)
  1122. )
  1123. this.editAliasRecipientsModalOpen = false
  1124. this.editAliasRecipientsLoading = false
  1125. this.recipientsAliasToEdit = {}
  1126. this.aliasRecipientsToEdit = []
  1127. this.success('Alias recipients updated')
  1128. })
  1129. .catch(error => {
  1130. this.editAliasRecipientsModalOpen = false
  1131. this.editAliasRecipientsLoading = false
  1132. this.recipientsAliasToEdit = {}
  1133. this.aliasRecipientsToEdit = []
  1134. this.error()
  1135. })
  1136. },
  1137. generateNewAlias() {
  1138. this.errors = {}
  1139. // Validate alias local part
  1140. if (
  1141. this.generateAliasFormat === 'custom' &&
  1142. !this.validLocalPart(this.generateAliasLocalPart)
  1143. ) {
  1144. return (this.errors.generateAliasLocalPart = 'Valid local part required')
  1145. }
  1146. if (this.generateAliasDescription.length > 200) {
  1147. return (this.errors.generateAliasDescription = 'Description cannot exceed 200 characters')
  1148. }
  1149. this.generateAliasLoading = true
  1150. axios
  1151. .post(
  1152. '/api/v1/aliases',
  1153. JSON.stringify({
  1154. domain: this.generateAliasDomain,
  1155. local_part: this.generateAliasLocalPart,
  1156. description: this.generateAliasDescription,
  1157. format: this.generateAliasFormat,
  1158. recipient_ids: this.generateAliasRecipientIds,
  1159. }),
  1160. {
  1161. headers: { 'Content-Type': 'application/json' },
  1162. }
  1163. )
  1164. .then(({ data }) => {
  1165. this.generateAliasLoading = false
  1166. this.generateAliasLocalPart = ''
  1167. this.generateAliasDescription = ''
  1168. this.generateAliasRecipientIds = []
  1169. this.rows.push(data.data)
  1170. this.generateAliasModalOpen = false
  1171. this.success('New alias generated successfully')
  1172. })
  1173. .catch(error => {
  1174. this.generateAliasLoading = false
  1175. if (error.response.status === 429) {
  1176. this.error('You have reached your hourly limit for creating new aliases')
  1177. } else {
  1178. this.error()
  1179. }
  1180. })
  1181. },
  1182. editAlias(alias) {
  1183. if (this.aliasDescriptionToEdit.length > 200) {
  1184. return this.error('Description cannot be more than 200 characters')
  1185. }
  1186. axios
  1187. .patch(
  1188. `/api/v1/aliases/${alias.id}`,
  1189. JSON.stringify({
  1190. description: this.aliasDescriptionToEdit,
  1191. }),
  1192. {
  1193. headers: { 'Content-Type': 'application/json' },
  1194. }
  1195. )
  1196. .then(response => {
  1197. alias.description = this.aliasDescriptionToEdit
  1198. this.aliasIdToEdit = ''
  1199. this.aliasDescriptionToEdit = ''
  1200. this.success('Alias description updated')
  1201. })
  1202. .catch(error => {
  1203. this.aliasIdToEdit = ''
  1204. this.aliasDescriptionToEdit = ''
  1205. this.error()
  1206. })
  1207. },
  1208. activateAlias(alias) {
  1209. axios
  1210. .post(
  1211. `/api/v1/active-aliases`,
  1212. JSON.stringify({
  1213. id: alias.id,
  1214. }),
  1215. {
  1216. headers: { 'Content-Type': 'application/json' },
  1217. }
  1218. )
  1219. .then(response => {
  1220. //
  1221. })
  1222. .catch(error => {
  1223. alias.active = false
  1224. if (error.response !== undefined) {
  1225. this.error(error.response.data)
  1226. } else {
  1227. this.error()
  1228. }
  1229. })
  1230. },
  1231. deactivateAlias(alias) {
  1232. axios
  1233. .delete(`/api/v1/active-aliases/${alias.id}`)
  1234. .then(response => {
  1235. //
  1236. })
  1237. .catch(error => {
  1238. alias.active = true
  1239. if (error.response !== undefined) {
  1240. this.error(error.response.data)
  1241. } else {
  1242. this.error()
  1243. }
  1244. })
  1245. },
  1246. displaySendFromAddress(alias) {
  1247. this.errors = {}
  1248. if (!this.validEmail(this.sendFromAliasDestination)) {
  1249. this.errors.sendFromAliasDestination = 'Valid Email required'
  1250. return
  1251. }
  1252. this.sendFromAliasEmailToSendTo = `${
  1253. alias.local_part
  1254. }+${this.sendFromAliasDestination.replace('@', '=')}@${alias.domain}`
  1255. },
  1256. setSendFromAliasCopied() {
  1257. this.sendFromAliasCopied = true
  1258. },
  1259. getAliasEmail(alias) {
  1260. return alias.extension
  1261. ? `${alias.local_part}+${alias.extension}@${alias.domain}`
  1262. : alias.email
  1263. },
  1264. getAliasLocalPart(alias) {
  1265. return alias.extension ? `${alias.local_part}+${alias.extension}` : alias.local_part
  1266. },
  1267. getAliasStatus(alias) {
  1268. if (alias.deleted_at) {
  1269. return {
  1270. colour: 'red',
  1271. status: 'Deleted',
  1272. }
  1273. } else {
  1274. return {
  1275. colour: alias.active ? 'green' : 'grey',
  1276. status: alias.active ? 'Active' : 'Inactive',
  1277. }
  1278. }
  1279. },
  1280. sortRecipients(x, y) {
  1281. return x.length < y.length ? -1 : x.length > y.length ? 1 : 0
  1282. },
  1283. has(object, path) {
  1284. return _.has(object, path)
  1285. },
  1286. validLocalPart(part) {
  1287. let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))$/
  1288. return re.test(part)
  1289. },
  1290. validEmail(email) {
  1291. let re =
  1292. /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  1293. return re.test(email)
  1294. },
  1295. clipboardSuccess() {
  1296. this.success('Copied to clipboard')
  1297. },
  1298. clipboardError() {
  1299. this.error('Could not copy to clipboard')
  1300. },
  1301. success(text = '') {
  1302. this.$notify({
  1303. title: 'Success',
  1304. text: text,
  1305. type: 'success',
  1306. })
  1307. },
  1308. error(text = 'An error has occurred, please try again later') {
  1309. this.$notify({
  1310. title: 'Error',
  1311. text: text,
  1312. type: 'error',
  1313. })
  1314. },
  1315. },
  1316. }
  1317. </script>
  1318. <style src="@vueform/multiselect/themes/default.css"></style>