1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066 |
- <template>
- <div>
- <div class="flex flex-wrap flex-row items-center justify-between mb-8 md:px-2 lg:px-6">
- <div
- 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"
- >
- <div class="p-4 flex items-center justify-between relative">
- <icon
- name="check-circle"
- class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
- />
- <div class="font-bold text-xl md:text-3xl text-indigo-800">
- {{ totalActive }}
- <p class="text-grey-200 text-sm tracking-wide uppercase">
- Active
- </p>
- </div>
- </div>
- </div>
- <div
- 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"
- >
- <div class="p-4 flex items-center justify-between relative">
- <icon
- name="cross-circle"
- class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
- />
- <div class="font-bold text-xl md:text-3xl text-indigo-800">
- {{ totalInactive }}
- <p class="text-grey-200 text-sm tracking-wide uppercase">
- Inactive
- </p>
- </div>
- </div>
- </div>
- <div
- 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"
- >
- <div class="p-4 flex items-center justify-between relative">
- <icon
- name="send"
- class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
- />
- <div class="font-bold text-xl md:text-3xl text-indigo-800">
- {{ totalForwarded }}
- <p class="text-grey-200 text-sm tracking-wide uppercase">
- Emails Forwarded
- </p>
- </div>
- </div>
- </div>
- <div
- 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"
- >
- <div class="p-4 flex items-center justify-between relative">
- <icon
- name="blocked"
- class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
- />
- <div class="font-bold text-xl md:text-3xl text-indigo-800">
- {{ totalBlocked }}
- <p class="text-grey-200 text-sm tracking-wide uppercase">
- Emails Blocked
- </p>
- </div>
- </div>
- </div>
- <div
- 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"
- >
- <div class="p-4 flex items-center justify-between relative">
- <icon
- name="corner-up-left"
- class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
- />
- <div class="font-bold text-xl md:text-3xl text-indigo-800">
- {{ totalReplies }}
- <p class="text-grey-200 text-sm tracking-wide uppercase">
- Email Replies
- </p>
- </div>
- </div>
- </div>
- <div
- 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"
- >
- <div class="p-4 flex items-center justify-between relative">
- <icon
- name="inbox"
- class="inline-block w-16 h-16 text-indigo-50 stroke-current absolute top-0 right-0"
- />
- <div class="font-bold text-xl md:text-3xl text-indigo-800">
- {{ bandwidthMb }}<span class="text-sm tracking-wide uppercase">MB</span>
- <p class="text-grey-200 text-sm tracking-wide uppercase">Bandwidth ({{ month }})</p>
- </div>
- </div>
- </div>
- </div>
- <div class="mb-6 flex flex-col md:flex-row justify-between md:items-center">
- <div class="relative">
- <input
- v-model="search"
- @keyup.esc="search = ''"
- tabindex="0"
- type="text"
- class="w-full md:w-64 appearance-none shadow bg-white text-grey-700 focus:outline-none rounded py-3 pl-3 pr-8"
- placeholder="Search Aliases"
- />
- <icon
- v-if="search"
- @click.native="search = ''"
- name="close-circle"
- class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
- />
- <icon
- v-else
- name="search"
- class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current pointer-events-none mr-2 flex items-center"
- />
- </div>
- <div class="flex flex-wrap mt-4 md:mt-0">
- <div class="block relative mr-4">
- <select
- v-model="showAliases"
- class="block appearance-none w-full text-grey-700 bg-white p-3 pr-8 rounded shadow focus:shadow-outline"
- required
- >
- <option value="without">Hide Deleted</option>
- <option value="with">Show Deleted</option>
- <option value="only">Deleted Only</option>
- </select>
- <div
- class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
- >
- <svg
- class="fill-current h-4 w-4"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- >
- <path
- d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
- />
- </svg>
- </div>
- </div>
- <div>
- <button
- @click="generateAliasModalOpen = true"
- class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none ml-auto"
- >
- Generate New Alias
- </button>
- </div>
- </div>
- </div>
- <vue-good-table
- v-if="initialAliases.length"
- @on-search="debounceToolips"
- @on-page-change="debounceToolips"
- :columns="columns"
- :rows="rows"
- :search-options="{
- enabled: true,
- skipDiacritics: true,
- externalQuery: search,
- }"
- :sort-options="{
- enabled: true,
- initialSortBy: { field: 'created_at', type: 'desc' },
- }"
- :pagination-options="{
- enabled: true,
- mode: 'pages',
- perPage: 25,
- perPageDropdown: [25, 50, 100],
- rowsPerPageLabel: 'Aliases per page',
- }"
- styleClass="vgt-table"
- >
- <div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
- No aliases found for that search!
- </div>
- <template slot="table-row" slot-scope="props">
- <span
- v-if="props.column.field == 'created_at'"
- class="tooltip outline-none text-sm"
- :data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
- >{{ props.row.created_at | timeAgo }}
- </span>
- <span v-else-if="props.column.field == 'email'" class="block">
- <span
- class="text-grey-400 tooltip cursor-pointer outline-none"
- data-tippy-content="Click to copy"
- v-clipboard="() => getAliasEmail(rows[props.row.originalIndex])"
- v-clipboard:success="clipboardSuccess"
- v-clipboard:error="clipboardError"
- ><span class="font-semibold text-indigo-800">{{
- getAliasLocalPart(props.row) | truncate(60)
- }}</span
- ><span v-if="getAliasLocalPart(props.row).length <= 60">{{
- ('@' + props.row.domain) | truncate(60 - getAliasLocalPart(props.row).length)
- }}</span>
- </span>
- <div v-if="aliasIdToEdit === props.row.id" class="flex items-center">
- <input
- @keyup.enter="editAlias(rows[props.row.originalIndex])"
- @keyup.esc="aliasIdToEdit = aliasDescriptionToEdit = ''"
- v-model="aliasDescriptionToEdit"
- type="text"
- class="flex-grow text-sm appearance-none bg-grey-100 border text-grey-700 focus:outline-none rounded px-2 py-1"
- :class="aliasDescriptionToEdit.length > 100 ? 'border-red-500' : 'border-transparent'"
- placeholder="Add description"
- tabindex="0"
- autofocus
- />
- <icon
- name="close"
- class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
- @click.native="aliasIdToEdit = aliasDescriptionToEdit = ''"
- />
- <icon
- name="save"
- class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
- @click.native="editAlias(rows[props.row.originalIndex])"
- />
- </div>
- <div v-else-if="props.row.description" class="flex items-center">
- <span class="inline-block text-grey-400 text-sm py-1 border border-transparent">
- {{ props.row.description | truncate(60) }}
- </span>
- <icon
- name="edit"
- class="inline-block w-6 h-6 ml-2 text-grey-200 fill-current cursor-pointer"
- @click.native="
- ;(aliasIdToEdit = props.row.id), (aliasDescriptionToEdit = props.row.description)
- "
- />
- </div>
- <div v-else>
- <span
- class="inline-block text-grey-200 text-sm cursor-pointer py-1 border border-transparent"
- @click=";(aliasIdToEdit = props.row.id), (aliasDescriptionToEdit = '')"
- >Add description</span
- >
- </div>
- </span>
- <span
- v-else-if="props.column.field == 'recipients'"
- class="flex items-center justify-center"
- >
- <span
- v-if="props.row.recipients.length && props.row.id !== recipientsAliasToEdit.id"
- class="inline-block tooltip outline-none font-semibold text-indigo-800"
- :data-tippy-content="recipientsTooltip(props.row.recipients)"
- >
- {{ props.row.recipients.length }}
- </span>
- <span v-else-if="props.row.id === recipientsAliasToEdit.id">{{
- aliasRecipientsToEdit.length ? aliasRecipientsToEdit.length : '1'
- }}</span>
- <span
- v-else-if="has(props.row.aliasable, 'default_recipient.email')"
- class="py-1 px-2 text-xs bg-yellow-200 text-yellow-900 rounded-full tooltip outline-none"
- :data-tippy-content="props.row.aliasable.default_recipient.email"
- >{{
- props.row.aliasable_type === 'App\\Models\\Domain' ? 'domain' : 'username'
- }}'s</span
- >
- <span
- v-else
- class="py-1 px-2 text-xs bg-yellow-200 text-yellow-900 rounded-full tooltip outline-none"
- :data-tippy-content="defaultRecipient.email"
- >default</span
- >
- <icon
- name="edit"
- class="ml-2 inline-block w-6 h-6 text-grey-200 fill-current cursor-pointer"
- @click.native="openAliasRecipientsModal(props.row)"
- />
- </span>
- <span
- v-else-if="props.column.field == 'emails_forwarded'"
- class="font-semibold text-indigo-800"
- >
- {{ props.row.emails_forwarded }}
- </span>
- <span
- v-else-if="props.column.field == 'emails_blocked'"
- class="font-semibold text-indigo-800"
- >
- {{ props.row.emails_blocked }}
- </span>
- <span
- v-else-if="props.column.field == 'emails_replied'"
- class="font-semibold text-indigo-800"
- >
- {{ props.row.emails_replied }} <span class="text-grey-200">/</span>
- {{ props.row.emails_sent }}
- </span>
- <span v-else-if="props.column.field === 'active'" class="flex items-center">
- <Toggle
- v-model="rows[props.row.originalIndex].active"
- @on="activateAlias(props.row.id)"
- @off="deactivateAlias(props.row.id)"
- />
- </span>
- <span v-else class="flex items-center justify-center outline-none" tabindex="-1">
- <icon
- v-if="props.row.deleted_at"
- name="undo"
- class="block w-6 h-6 text-grey-200 fill-current cursor-pointer outline-none"
- @click.native="openRestoreModal(props.row.id)"
- />
- <icon
- v-else
- name="trash"
- class="block w-6 h-6 text-grey-200 fill-current cursor-pointer outline-none"
- @click.native="openDeleteModal(props.row.id)"
- />
- </span>
- </template>
- </vue-good-table>
- <div v-else class="bg-white rounded shadow overflow-x-auto">
- <div class="p-8 text-center text-lg text-grey-700">
- <h1 class="mb-6 text-2xl text-indigo-800 font-semibold">
- It doesn't look like you have any aliases yet!
- </h1>
- <div class="mx-auto mb-6 w-24 border-b-2 border-grey-200"></div>
- <p class="mb-4">
- There are two ways to create new aliases.
- </p>
- <h3 class="mb-4 text-xl text-indigo-800 font-semibold">
- Option 1: Create aliases on the fly
- </h3>
- <p class="mb-4">
- To create aliases on the fly all you have to do is make up any new alias and give that out
- instead of your real email address.
- </p>
- <p class="mb-4">
- Let's say you're signing up to <b>example.com</b> you could enter
- <b>example@{{ subdomain }}</b> as your email address.
- </p>
- <p class="mb-4">
- The alias will show up here automatically as soon as it has forwarded its first email.
- </p>
- <p class="mb-4">
- If you start receiving spam to the alias you can simply deactivate it or delete it all
- together!
- </p>
- <p class="mb-4">
- Try it out now by sending an email to <b>first@{{ subdomain }}</b> and then refresh this
- page.
- </p>
- <h3 class="mb-4 text-xl text-indigo-800 font-semibold">
- Option 2: Generate a unique random alias
- </h3>
- <p class="mb-4">
- You can click the button above to generate a random alias that will look something like
- this:
- </p>
- <p class="mb-4">
- <b>86064c92-da41-443e-a2bf-5a7b0247842f@{{ domain }}</b>
- </p>
- <p>
- Useful if you do not wish to include your username in the email as a potential link
- between aliases.
- </p>
- </div>
- </div>
- <Modal :open="generateAliasModalOpen" @close="generateAliasModalOpen = false">
- <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Generate new alias
- </h2>
- <p class="mt-4 text-grey-700">
- Other aliases e.g. alias@{{ subdomain }} can also be created automatically when they
- receive their first email.
- </p>
- <label for="alias_domain" class="block text-grey-700 text-sm my-2">
- Alias Domain:
- </label>
- <div class="block relative w-full mb-4">
- <select
- v-model="generateAliasDomain"
- id="alias_domain"
- class="block appearance-none w-full text-grey-700 bg-grey-100 p-3 pr-8 rounded shadow focus:shadow-outline"
- required
- >
- <option
- v-for="domainOption in domainOptions"
- :key="domainOption"
- :value="domainOption"
- >{{ domainOption }}</option
- >
- </select>
- <div
- class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
- >
- <svg
- class="fill-current h-4 w-4"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- >
- <path
- d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
- />
- </svg>
- </div>
- </div>
- <label for="alias_domain" class="block text-grey-700 text-sm mt-4 mb-2">
- Alias Format:
- </label>
- <div class="block relative w-full mb-4">
- <select
- v-model="generateAliasFormat"
- id="alias_domain"
- class="block appearance-none w-full text-grey-700 bg-grey-100 p-3 pr-8 rounded shadow focus:shadow-outline"
- required
- >
- <option
- v-for="formatOption in aliasFormatOptions"
- :key="formatOption.value"
- :value="formatOption.value"
- >{{ formatOption.label }}</option
- >
- </select>
- <div
- class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
- >
- <svg
- class="fill-current h-4 w-4"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- >
- <path
- d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
- />
- </svg>
- </div>
- </div>
- <div v-if="generateAliasFormat === 'custom'">
- <label for="alias_local_part" class="block text-grey-700 text-sm my-2">
- Alias Local Part:
- </label>
- <p v-show="errors.generateAliasLocalPart" class="mb-3 text-red-500 text-sm">
- {{ errors.generateAliasLocalPart }}
- </p>
- <input
- v-model="generateAliasLocalPart"
- id="alias_local_part"
- type="text"
- class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3"
- :class="errors.generateAliasLocalPart ? 'border-red-500' : ''"
- placeholder="Enter local part..."
- autofocus
- />
- </div>
- <label for="alias_description" class="block text-grey-700 text-sm my-2">
- Description:
- </label>
- <p v-show="errors.generateAliasDescription" class="mb-3 text-red-500 text-sm">
- {{ errors.generateAliasDescription }}
- </p>
- <input
- v-model="generateAliasDescription"
- id="alias_description"
- type="text"
- class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-3"
- :class="errors.generateAliasDescription ? 'border-red-500' : ''"
- placeholder="Enter description (optional)..."
- autofocus
- />
- <div class="mt-6">
- <button
- @click="generateNewAlias"
- class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
- :class="generateAliasLoading ? 'cursor-not-allowed' : ''"
- :disabled="generateAliasLoading"
- >
- Generate Alias
- <loader v-if="generateAliasLoading" />
- </button>
- <button
- @click="generateAliasModalOpen = false"
- 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"
- >
- Cancel
- </button>
- </div>
- </div>
- </Modal>
- <Modal :open="editAliasRecipientsModalOpen" @close="closeAliasRecipientsModal">
- <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Update Alias Recipients
- </h2>
- <p class="my-4 text-grey-700">
- Select the recipients for this alias. You can choose multiple recipients. Leave it empty
- if you would like to use the default recipient.
- </p>
- <multiselect
- v-model="aliasRecipientsToEdit"
- :options="recipientOptions"
- :multiple="true"
- :close-on-select="true"
- :clear-on-select="false"
- :searchable="true"
- :max="10"
- placeholder="Select recipients"
- label="email"
- track-by="email"
- :preselect-first="false"
- :show-labels="false"
- >
- </multiselect>
- <div class="mt-6">
- <button
- type="button"
- @click="editAliasRecipients()"
- class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
- :class="editAliasRecipientsLoading ? 'cursor-not-allowed' : ''"
- :disabled="editAliasRecipientsLoading"
- >
- Update Recipients
- <loader v-if="editAliasRecipientsLoading" />
- </button>
- <button
- @click="closeAliasRecipientsModal()"
- 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"
- >
- Cancel
- </button>
- </div>
- </div>
- </Modal>
- <Modal :open="restoreAliasModalOpen" @close="closeRestoreModal">
- <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Restore alias
- </h2>
- <p class="mt-4 text-grey-700">
- Are you sure you want to restore this alias? Once restored, this alias will
- <b>be able to receive emails again</b>.
- </p>
- <div class="mt-6">
- <button
- type="button"
- @click="restoreAlias(aliasIdToRestore)"
- class="px-4 py-3 text-cyan-900 font-semibold bg-cyan-400 hover:bg-cyan-300 border border-transparent rounded focus:outline-none"
- :class="restoreAliasLoading ? 'cursor-not-allowed' : ''"
- :disabled="restoreAliasLoading"
- >
- Restore alias
- <loader v-if="restoreAliasLoading" />
- </button>
- <button
- @click="closeRestoreModal"
- 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"
- >
- Cancel
- </button>
- </div>
- </div>
- </Modal>
- <Modal :open="deleteAliasModalOpen" @close="closeDeleteModal">
- <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
- <h2
- class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
- >
- Delete alias
- </h2>
- <p class="mt-4 text-grey-700">
- Are you sure you want to delete this alias? <b>You can restore this alias</b> if you later
- change your mind. Once deleted, this alias will <b>reject any emails sent to it</b>.
- </p>
- <div class="mt-6">
- <button
- type="button"
- @click="deleteAlias(aliasIdToDelete)"
- class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
- :class="deleteAliasLoading ? 'cursor-not-allowed' : ''"
- :disabled="deleteAliasLoading"
- >
- Delete alias
- <loader v-if="deleteAliasLoading" />
- </button>
- <button
- @click="closeDeleteModal"
- 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"
- >
- Cancel
- </button>
- </div>
- </div>
- </Modal>
- </div>
- </template>
- <script>
- import Modal from './../components/Modal.vue'
- import Toggle from './../components/Toggle.vue'
- import tippy from 'tippy.js'
- import Multiselect from 'vue-multiselect'
- export default {
- props: {
- defaultRecipient: {
- type: Object,
- required: true,
- },
- initialAliases: {
- type: Array,
- required: true,
- },
- recipientOptions: {
- type: Array,
- required: true,
- },
- totalForwarded: {
- type: Number,
- required: true,
- },
- totalBlocked: {
- type: Number,
- required: true,
- },
- totalReplies: {
- type: Number,
- required: true,
- },
- domain: {
- type: String,
- required: true,
- },
- subdomain: {
- type: String,
- required: true,
- },
- bandwidthMb: {
- type: Number,
- required: true,
- },
- month: {
- type: String,
- required: true,
- },
- domainOptions: {
- type: Array,
- required: true,
- },
- defaultAliasDomain: {
- type: String,
- required: true,
- },
- defaultAliasFormat: {
- type: String,
- required: true,
- },
- },
- components: {
- Modal,
- Toggle,
- Multiselect,
- },
- mounted() {
- this.addTooltips()
- },
- data() {
- return {
- search: '',
- showAliases: 'without',
- aliasIdToEdit: '',
- aliasDescriptionToEdit: '',
- aliasIdToDelete: '',
- aliasIdToRestore: '',
- deleteAliasLoading: false,
- deleteAliasModalOpen: false,
- restoreAliasLoading: false,
- restoreAliasModalOpen: false,
- editAliasRecipientsLoading: false,
- editAliasRecipientsModalOpen: false,
- generateAliasModalOpen: false,
- generateAliasLoading: false,
- generateAliasDomain: this.defaultAliasDomain ? this.defaultAliasDomain : this.domain,
- generateAliasLocalPart: '',
- generateAliasDescription: '',
- generateAliasFormat: this.defaultAliasFormat ? this.defaultAliasFormat : 'uuid',
- aliasFormatOptions: [
- {
- value: 'uuid',
- label: 'UUID',
- },
- {
- value: 'random_words',
- label: 'Random Words',
- },
- {
- value: 'custom',
- label: 'Custom',
- },
- ],
- recipientsAliasToEdit: {},
- aliasRecipientsToEdit: [],
- columns: [
- {
- label: 'Created',
- field: 'created_at',
- globalSearchDisabled: true,
- },
- {
- label: 'Alias',
- field: 'email',
- },
- {
- label: 'Recipients',
- field: 'recipients',
- tdClass: 'text-center',
- sortable: true,
- sortFn: this.sortRecipients,
- globalSearchDisabled: true,
- },
- {
- label: 'Description',
- field: 'description',
- sortable: false,
- hidden: true,
- },
- {
- label: 'Forwarded',
- field: 'emails_forwarded',
- type: 'number',
- tdClass: 'text-center',
- globalSearchDisabled: true,
- },
- {
- label: 'Blocked',
- field: 'emails_blocked',
- type: 'number',
- tdClass: 'text-center',
- globalSearchDisabled: true,
- },
- {
- label: 'Replies/Sent',
- field: 'emails_replied',
- type: 'number',
- tdClass: 'text-center',
- globalSearchDisabled: true,
- },
- {
- label: 'Active',
- field: 'active',
- type: 'boolean',
- globalSearchDisabled: true,
- },
- {
- label: '',
- field: 'actions',
- sortable: false,
- globalSearchDisabled: true,
- },
- ],
- rows: this.initialAliases,
- errors: {},
- }
- },
- watch: {
- aliasIdToEdit: _.debounce(function() {
- this.addTooltips()
- }, 50),
- editAliasRecipientsModalOpen: _.debounce(function() {
- this.addTooltips()
- }, 50),
- showAliases(value) {
- this.updateAliases()
- },
- },
- computed: {
- activeUuidAliases() {
- return _.filter(this.rows, alias => alias.id === alias.local_part && alias.active)
- },
- totalActive() {
- return _.filter(this.rows, 'active').length
- },
- totalInactive() {
- return _.reject(this.rows, 'active').length
- },
- },
- methods: {
- addTooltips() {
- tippy('.tooltip', {
- arrow: true,
- arrowType: 'round',
- })
- },
- debounceToolips: _.debounce(function() {
- this.addTooltips()
- }, 50),
- recipientsTooltip(recipients) {
- return _.reduce(recipients, (list, recipient) => list + `${recipient.email}<br>`, '')
- },
- openDeleteModal(id) {
- this.deleteAliasModalOpen = true
- this.aliasIdToDelete = id
- },
- closeDeleteModal() {
- this.deleteAliasModalOpen = false
- this.aliasIdToDelete = ''
- },
- openRestoreModal(id) {
- this.restoreAliasModalOpen = true
- this.aliasIdToRestore = id
- },
- closeRestoreModal() {
- this.restoreAliasModalOpen = false
- this.aliasIdToRestore = ''
- },
- updateAliases() {
- axios
- .get(`/api/v1/aliases?deleted=${this.showAliases}`, {
- headers: { 'Content-Type': 'application/json' },
- })
- .then(response => {
- this.rows = response.data.data
- })
- .catch(error => {
- this.error()
- })
- },
- deleteAlias(id) {
- this.deleteAliasLoading = true
- axios
- .delete(`/api/v1/aliases/${id}`)
- .then(response => {
- this.rows = _.reject(this.rows, alias => alias.id === id)
- this.deleteAliasModalOpen = false
- this.deleteAliasLoading = false
- })
- .catch(error => {
- this.error()
- this.deleteAliasModalOpen = false
- this.deleteAliasLoading = false
- })
- },
- restoreAlias(id) {
- this.restoreAliasLoading = true
- axios
- .patch(`/api/v1/aliases/${id}/restore`, {
- headers: { 'Content-Type': 'application/json' },
- })
- .then(response => {
- this.updateAliases()
- this.restoreAliasModalOpen = false
- this.restoreAliasLoading = false
- this.success('Alias restored successfully')
- })
- .catch(error => {
- this.error()
- this.restoreAliasModalOpen = false
- this.restoreAliasLoading = false
- })
- },
- openAliasRecipientsModal(alias) {
- this.editAliasRecipientsModalOpen = true
- this.recipientsAliasToEdit = alias
- this.aliasRecipientsToEdit = alias.recipients
- },
- closeAliasRecipientsModal() {
- this.editAliasRecipientsModalOpen = false
- this.recipientsAliasToEdit = {}
- this.aliasRecipientsToEdit = []
- },
- editAliasRecipients() {
- this.editAliasRecipientsLoading = true
- axios
- .post(
- '/api/v1/alias-recipients',
- JSON.stringify({
- alias_id: this.recipientsAliasToEdit.id,
- recipient_ids: _.map(this.aliasRecipientsToEdit, recipient => recipient.id),
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(response => {
- let alias = _.find(this.rows, ['id', this.recipientsAliasToEdit.id])
- alias.recipients = this.aliasRecipientsToEdit
- this.editAliasRecipientsModalOpen = false
- this.editAliasRecipientsLoading = false
- this.recipientsAliasToEdit = {}
- this.aliasRecipientsToEdit = []
- this.success('Alias recipients updated')
- })
- .catch(error => {
- this.editAliasRecipientsModalOpen = false
- this.editAliasRecipientsLoading = false
- this.recipientsAliasToEdit = {}
- this.aliasRecipientsToEdit = []
- this.error()
- })
- },
- generateNewAlias() {
- this.errors = {}
- // Validate alias local part
- if (
- this.generateAliasFormat === 'custom' &&
- !this.validLocalPart(this.generateAliasLocalPart)
- ) {
- return (this.errors.generateAliasLocalPart = 'Valid local part required')
- }
- if (this.generateAliasDescription.length > 100) {
- return (this.errors.generateAliasDescription = 'Description cannot exceed 100 characters')
- }
- this.generateAliasLoading = true
- axios
- .post(
- '/api/v1/aliases',
- JSON.stringify({
- domain: this.generateAliasDomain,
- local_part: this.generateAliasLocalPart,
- description: this.generateAliasDescription,
- format: this.generateAliasFormat,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(({ data }) => {
- this.generateAliasLoading = false
- this.generateAliasLocalPart = ''
- this.generateAliasDescription = ''
- this.rows.push(data.data)
- this.generateAliasModalOpen = false
- this.success('New alias generated successfully')
- })
- .catch(error => {
- this.generateAliasLoading = false
- if (error.response.status === 429) {
- this.error('You have reached your hourly limit for creating new aliases')
- } else {
- this.error()
- }
- })
- },
- editAlias(alias) {
- if (this.aliasDescriptionToEdit.length > 100) {
- return this.error('Description cannot be more than 100 characters')
- }
- axios
- .patch(
- `/api/v1/aliases/${alias.id}`,
- JSON.stringify({
- description: this.aliasDescriptionToEdit,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(response => {
- alias.description = this.aliasDescriptionToEdit
- this.aliasIdToEdit = ''
- this.aliasDescriptionToEdit = ''
- this.success('Alias description updated')
- })
- .catch(error => {
- this.aliasIdToEdit = ''
- this.aliasDescriptionToEdit = ''
- this.error()
- })
- },
- activateAlias(id) {
- axios
- .post(
- `/api/v1/active-aliases`,
- JSON.stringify({
- id: id,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- }
- )
- .then(response => {
- //
- })
- .catch(error => {
- this.error()
- })
- },
- deactivateAlias(id) {
- axios
- .delete(`/api/v1/active-aliases/${id}`)
- .then(response => {
- //
- })
- .catch(error => {
- this.error()
- })
- },
- getAliasEmail(alias) {
- return alias.extension
- ? `${alias.local_part}+${alias.extension}@${alias.domain}`
- : alias.email
- },
- getAliasLocalPart(alias) {
- return alias.extension ? `${alias.local_part}+${alias.extension}` : alias.local_part
- },
- sortRecipients(x, y) {
- return x.length < y.length ? -1 : x.length > y.length ? 1 : 0
- },
- has(object, path) {
- return _.has(object, path)
- },
- validLocalPart(part) {
- let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))$/
- return re.test(part)
- },
- clipboardSuccess() {
- this.success('Copied to clipboard')
- },
- clipboardError() {
- this.error('Could not copy to clipboard')
- },
- success(text = '') {
- this.$notify({
- title: 'Success',
- text: text,
- type: 'success',
- })
- },
- error(text = 'An error has occurred, please try again later') {
- this.$notify({
- title: 'Error',
- text: text,
- type: 'error',
- })
- },
- },
- }
- </script>
- <style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|