12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391 |
- <template>
- <div>
- <Head :title="$page.component" />
- <h1 id="primary-heading" class="sr-only">
- {{ $page.component }}
- </h1>
- <div class="sm:flex sm:items-center mb-6">
- <div class="sm:flex-auto">
- <h1 class="text-2xl font-semibold text-grey-900">Rules</h1>
- <p class="mt-2 text-sm text-grey-700">
- A list of all the rules {{ search ? 'found for your search' : 'in your account' }}
- <button @click="moreInfoOpen = !moreInfoOpen">
- <InformationCircleIcon
- class="h-6 w-6 inline-block cursor-pointer text-grey-500"
- title="Click for more information"
- />
- </button>
- </p>
- </div>
- <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
- <button
- type="button"
- @click="openCreateModal"
- class="inline-flex items-center justify-center rounded-md border border-transparent bg-cyan-400 hover:bg-cyan-300 text-cyan-900 px-4 py-2 font-bold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:w-auto"
- >
- Create Rule
- </button>
- </div>
- </div>
- <div v-if="rows.length" class="bg-white shadow">
- <div class="vgt-responsive">
- <table class="table-auto w-full">
- <thead class="border-b border-grey-100 text-grey-400">
- <tr>
- <th scope="col" class="p-3"></th>
- <th scope="col" class="p-3 text-left">Created</th>
- <th scope="col" class="p-3 text-left">Name</th>
- <th scope="col" class="p-3 text-left">Active</th>
- <th scope="col" class="p-3 text-left">
- Applied
- <span
- class="tooltip outline-none"
- data-tippy-content="This is the number of times that the rule has been applied. Hover over the count to see when it was last applied."
- >
- <icon name="info" class="inline-block w-4 h-4 text-grey-300 fill-current" />
- </span>
- </th>
- <th scope="col" class="p-3"></th>
- </tr>
- </thead>
- <draggable
- :component-data="{ type: 'transition', name: 'flip-list' }"
- v-model="rows"
- item-key="id"
- tag="tbody"
- handle=".handle"
- :group="{ name: 'description' }"
- ghost-class="ghost"
- @change="reorderRules"
- @update="debounceToolips"
- >
- <template #item="{ element }">
- <tr class="border-b border-grey-100 h-20">
- <td scope="row" class="p-3">
- <icon
- name="menu"
- class="handle block w-6 h-6 text-grey-300 fill-current cursor-pointer"
- />
- </td>
- <td scope="row" class="p-3">
- <span
- class="tooltip outline-none cursor-default text-sm text-grey-500"
- :data-tippy-content="$filters.formatDate(element.created_at)"
- >{{ $filters.timeAgo(element.created_at) }}
- </span>
- </td>
- <td scope="row" class="p-3">
- <span class="font-medium text-grey-700">{{ element.name }}</span>
- </td>
- <td scope="row" class="p-3">
- <Toggle
- v-model="element.active"
- @on="activateRule(element.id)"
- @off="deactivateRule(element.id)"
- />
- </td>
- <td scope="row" class="p-3">
- <span
- v-if="element.last_applied"
- class="tooltip outline-none cursor-default font-semibold text-indigo-800"
- :data-tippy-content="
- $filters.timeAgo(element.last_applied) +
- ' (' +
- $filters.formatDate(element.last_applied) +
- ')'
- "
- >{{ element.applied.toLocaleString() }}
- </span>
- <span v-else>{{ element.applied.toLocaleString() }} </span>
- </td>
- <td scope="row" class="p-3 text-right w-0 min-w-fit whitespace-nowrap">
- <button
- @click="openEditModal(element)"
- as="button"
- type="button"
- class="text-indigo-500 hover:text-indigo-800 font-medium"
- >
- Edit
- </button>
- <button
- @click="openDeleteModal(element.id)"
- as="button"
- type="button"
- class="text-indigo-500 hover:text-indigo-800 font-medium ml-4"
- >
- Delete
- </button>
- </td>
- </tr>
- </template>
- </draggable>
- </table>
- </div>
- </div>
- <div v-else-if="search" class="text-center">
- <FunnelIcon class="mx-auto h-16 w-16 text-grey-400" />
- <h3 class="mt-2 text-lg font-medium text-grey-900">No Rules found for that search</h3>
- <p class="mt-1 text-md text-grey-500">Try entering a different search term.</p>
- <div class="mt-6">
- <Link
- :href="route('rules.index')"
- type="button"
- class="inline-flex items-center rounded-md border border-transparent bg-cyan-400 hover:bg-cyan-300 text-cyan-900 px-4 py-2 text-sm font-medium shadow-sm focus:outline-none"
- >
- View All Rules
- </Link>
- </div>
- </div>
- <div v-else class="text-center">
- <FunnelIcon class="mx-auto h-16 w-16 text-grey-400" />
- <h3 class="mt-2 text-lg font-medium text-grey-900">No Rules</h3>
- <p class="mt-1 text-md text-grey-500">Get started by creating a new rule.</p>
- <div class="mt-6">
- <button
- @click="openCreateModal"
- type="button"
- class="inline-flex items-center rounded-md border border-transparent bg-cyan-400 hover:bg-cyan-300 text-cyan-900 px-4 py-2 text-sm font-medium shadow-sm focus:outline-none"
- >
- <PlusIcon class="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
- Create a Rule
- </button>
- </div>
- </div>
- <Modal
- :open="createRuleModalOpen"
- @close="createRuleModalOpen = false"
- max-width="md:max-w-3xl"
- >
- <template v-slot:title> Create new rule </template>
- <template v-slot:content>
- <p class="mt-4 text-grey-700">
- Rules work on all emails, including replies and also send froms. New conditions and
- actions will be added over time.
- </p>
- <label for="rule_name" class="block font-medium leading-6 text-grey-600 text-sm my-2">
- Name
- </label>
- <p v-show="errors.ruleName" class="mb-3 text-red-500 text-sm">
- {{ errors.ruleName }}
- </p>
- <input
- v-model="createRuleObject.name"
- id="rule_name"
- type="text"
- class="block w-full rounded-md border-0 py-2 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-base sm:leading-6"
- :class="errors.ruleName ? 'ring-red-500' : ''"
- placeholder="Enter name"
- autofocus
- />
- <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
- <legend class="px-2 leading-none text-sm">Conditions</legend>
- <!-- Loop for conditions -->
- <div v-for="(condition, key) in createRuleObject.conditions" :key="key">
- <!-- AND/OR operator -->
- <div v-if="key !== 0" class="flex justify-center my-2">
- <div class="relative">
- <select
- v-model="createRuleObject.operator"
- :id="`create_rule_operator_${key}`"
- class="block appearance-none w-full text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option value="AND">AND</option>
- <option value="OR">OR</option>
- </select>
- </div>
- </div>
- <div class="p-2 w-full bg-grey-100">
- <div class="flex">
- <div
- class="w-full flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0"
- >
- <span>If the</span>
- <span class="sm:ml-2">
- <div class="relative">
- <select
- v-model="createRuleObject.conditions[key].type"
- :id="`create_rule_condition_types_${key}`"
- class="block appearance-none w-full sm:w-32 text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option
- v-for="option in conditionTypeOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </option>
- </select>
- </div>
- </span>
- <span
- v-if="conditionMatchOptions(createRuleObject, key).length"
- class="sm:ml-4 flex flex-col sm:flex-row space-y-2 sm:space-y-0"
- >
- <div class="relative sm:mr-4">
- <select
- v-model="createRuleObject.conditions[key].match"
- :id="`create_rule_condition_matches_${key}`"
- class="block appearance-none w-full sm:w-40 text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option
- v-for="option in conditionMatchOptions(createRuleObject, key)"
- :key="option"
- :value="option"
- >
- {{ option }}
- </option>
- </select>
- </div>
- <div class="flex">
- <input
- v-model="createRuleObject.conditions[key].currentConditionValue"
- @keyup.enter="addValueToCondition(createRuleObect, key)"
- type="text"
- class="w-full appearance-none bg-white border border-transparent rounded-l text-grey-700 focus:outline-none p-2"
- :class="errors.ruleConditions ? 'border-red-500' : ''"
- placeholder="Enter value"
- autofocus
- />
- <button
- @click="addValueToCondition(createRuleObject, key)"
- class="p-2 bg-grey-200 rounded-r text-grey-600"
- >
- Insert
- </button>
- </div>
- </span>
- </div>
- <div class="flex items-center">
- <!-- delete button -->
- <icon
- v-if="createRuleObject.conditions.length > 1"
- name="trash"
- class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
- @click="deleteCondition(createRuleObject, key)"
- />
- </div>
- </div>
- <div class="mt-2 text-left">
- <span
- v-for="(value, index) in createRuleObject.conditions[key].values"
- :key="index"
- >
- <span class="bg-green-200 text-sm font-semibold rounded-sm pl-1 text-nowrap">
- {{ value }}
- <icon
- name="close"
- class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
- @click="createRuleObject.conditions[key].values.splice(index, 1)"
- />
- </span>
- <span
- class="mx-1"
- v-if="index + 1 !== createRuleObject.conditions[key].values.length"
- >
- or
- </span>
- </span>
- </div>
- </div>
- </div>
- <!-- add condition button -->
- <button
- @click="addCondition(createRuleObject)"
- class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Add condition
- </button>
- <p v-show="errors.ruleConditions" class="mt-2 text-red-500 text-sm">
- {{ errors.ruleConditions }}
- </p>
- </fieldset>
- <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
- <legend class="px-2 leading-none text-sm">Actions</legend>
- <!-- Loop for actions -->
- <div v-for="(action, key) in createRuleObject.actions" :key="key">
- <!-- AND/OR operator -->
- <div v-if="key !== 0" class="flex justify-center my-2">
- <div class="relative">AND</div>
- </div>
- <div class="p-2 w-full bg-grey-100">
- <div class="flex">
- <div
- class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:items-center w-full"
- >
- <span>Then</span>
- <span class="sm:ml-2">
- <div class="relative">
- <select
- v-model="createRuleObject.actions[key].type"
- @change="ruleActionChange(createRuleObject.actions[key])"
- :id="`rule_action_types_${key}`"
- class="w-full block appearance-none text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option
- v-for="option in actionTypeOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </option>
- </select>
- </div>
- </span>
- <span
- v-if="
- createRuleObject.actions[key].type === 'subject' ||
- createRuleObject.actions[key].type === 'displayFrom'
- "
- class="sm:ml-4 flex"
- >
- <div class="flex w-full">
- <input
- v-model="createRuleObject.actions[key].value"
- type="text"
- class="w-full appearance-none bg-white border border-transparent rounded text-grey-700 focus:outline-none p-2"
- :class="errors.ruleActions ? 'border-red-500' : ''"
- placeholder="Enter value"
- autofocus
- />
- </div>
- </span>
- <multiselect
- v-if="createRuleObject.actions[key].type === 'forwardTo'"
- class="sm:!ml-4 flex"
- v-model="createRuleObject.actions[key].value"
- :options="recipientOptions"
- mode="single"
- value-prop="id"
- :close-on-select="true"
- :clear-on-select="false"
- :searchable="false"
- :allow-empty="true"
- placeholder="Select recipient"
- label="email"
- track-by="email"
- >
- </multiselect>
- <span
- v-else-if="createRuleObject.actions[key].type === 'banner'"
- class="sm:ml-4 flex"
- >
- <div class="relative sm:mr-4 w-full">
- <select
- v-model="createRuleObject.actions[key].value"
- :id="`create_rule_action_banner_${key}`"
- class="w-full block appearance-none sm:w-40 text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option value="top">Top</option>
- <option value="bottom">Bottom</option>
- <option value="off">Off</option>
- </select>
- </div>
- </span>
- </div>
- <div class="flex items-center">
- <!-- delete button -->
- <icon
- v-if="createRuleObject.actions.length > 1"
- name="trash"
- class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
- @click="deleteAction(createRuleObject, key)"
- />
- </div>
- </div>
- </div>
- </div>
- <!-- add action button -->
- <button
- @click="addAction(createRuleObject)"
- class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Add action
- </button>
- <p v-show="errors.ruleActions" class="mt-2 text-red-500 text-sm">
- {{ errors.ruleActions }}
- </p>
- </fieldset>
- <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
- <legend class="px-2 leading-none text-sm">Apply rule on</legend>
- <div class="w-full flex">
- <div class="relative flex items-center">
- <input
- v-model="createRuleObject.forwards"
- id="forwards"
- name="forwards"
- type="checkbox"
- class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-grey-300 rounded"
- />
- <label for="forwards" class="ml-2 text-sm text-grey-700">Forwards</label>
- </div>
- <div class="relative flex items-center mx-4">
- <input
- v-model="createRuleObject.replies"
- id="replies"
- name="replies"
- type="checkbox"
- class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-grey-300 rounded"
- />
- <label for="replies" class="ml-2 text-sm text-grey-700">Replies</label>
- </div>
- <div class="relative flex items-center">
- <input
- v-model="createRuleObject.sends"
- id="sends"
- name="sends"
- type="checkbox"
- class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-grey-300 rounded"
- />
- <label for="sends" class="ml-2 text-sm text-grey-700">Sends</label>
- </div>
- </div>
- </fieldset>
- <div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
- <button
- @click="createNewRule"
- class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
- :disabled="createRuleLoading"
- >
- Create Rule
- <loader v-if="createRuleLoading" />
- </button>
- <button
- @click="createRuleModalOpen = false"
- class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Cancel
- </button>
- </div>
- </template>
- </Modal>
- <Modal :open="editRuleModalOpen" @close="closeEditModal" max-width="md:max-w-3xl">
- <template v-slot:title> Edit rule </template>
- <template v-slot:content>
- <p class="mt-4 text-grey-700">
- Rules work on all emails, including replies and also send froms. New conditions and
- actions will be added over time.
- </p>
- <label for="edit_rule_name" class="block font-medium leading-6 text-grey-600 text-sm my-2">
- Name
- </label>
- <p v-show="errors.ruleName" class="mb-3 text-red-500 text-sm">
- {{ errors.ruleName }}
- </p>
- <input
- v-model="editRuleObject.name"
- id="edit_rule_name"
- type="text"
- class="block w-full rounded-md border-0 py-2 pr-10 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-base sm:leading-6"
- :class="errors.ruleName ? 'ring-red-500' : ''"
- placeholder="Enter name"
- autofocus
- />
- <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
- <legend class="px-2 leading-none text-sm">Conditions</legend>
- <!-- Loop for conditions -->
- <div v-for="(condition, key) in editRuleObject.conditions" :key="key">
- <!-- AND/OR operator -->
- <div v-if="key !== 0" class="flex justify-center my-2">
- <div class="relative">
- <select
- v-model="editRuleObject.operator"
- :id="`edit_rule_operator_${key}`"
- class="block appearance-none w-full text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option value="AND">AND</option>
- <option value="OR">OR</option>
- </select>
- </div>
- </div>
- <div class="p-2 w-full bg-grey-100">
- <div class="flex">
- <div
- class="w-full flex flex-col sm:flex-row sm:items-center space-y-2 sm:space-y-0"
- >
- <span>If the</span>
- <span class="sm:ml-2">
- <div class="relative">
- <select
- v-model="editRuleObject.conditions[key].type"
- :id="`edit_rule_condition_types_${key}`"
- class="block appearance-none w-full sm:w-32 text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option
- v-for="option in conditionTypeOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </option>
- </select>
- </div>
- </span>
- <span
- v-if="conditionMatchOptions(editRuleObject, key).length"
- class="sm:ml-4 flex flex-col sm:flex-row space-y-2 sm:space-y-0"
- >
- <div class="relative sm:mr-4">
- <select
- v-model="editRuleObject.conditions[key].match"
- :id="`edit_rule_condition_matches_${key}`"
- class="block appearance-none w-full sm:w-40 text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option
- v-for="option in conditionMatchOptions(editRuleObject, key)"
- :key="option"
- :value="option"
- >
- {{ option }}
- </option>
- </select>
- </div>
- <div class="flex">
- <input
- v-model="editRuleObject.conditions[key].currentConditionValue"
- @keyup.enter="addValueToCondition(editRuleObect, key)"
- type="text"
- class="w-full appearance-none bg-white border border-transparent rounded-l text-grey-700 focus:outline-none p-2"
- :class="errors.ruleConditions ? 'border-red-500' : ''"
- placeholder="Enter value"
- autofocus
- />
- <button
- @click="addValueToCondition(editRuleObject, key)"
- class="p-2 bg-grey-200 rounded-r text-grey-600"
- >
- Insert
- </button>
- </div>
- </span>
- </div>
- <div class="flex items-center">
- <!-- delete button -->
- <icon
- v-if="editRuleObject.conditions.length > 1"
- name="trash"
- class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
- @click="deleteCondition(editRuleObject, key)"
- />
- </div>
- </div>
- <div class="mt-2 text-left">
- <span v-for="(value, index) in editRuleObject.conditions[key].values" :key="index">
- <span class="bg-green-200 text-sm font-semibold rounded-sm pl-1 text-nowrap">
- {{ value }}
- <icon
- name="close"
- class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
- @click="editRuleObject.conditions[key].values.splice(index, 1)"
- />
- </span>
- <span
- class="mx-1"
- v-if="index + 1 !== editRuleObject.conditions[key].values.length"
- >
- or
- </span>
- </span>
- </div>
- </div>
- </div>
- <!-- add condition button -->
- <button
- @click="addCondition(editRuleObject)"
- class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Add condition
- </button>
- <p v-show="errors.ruleConditions" class="mt-2 text-red-500 text-sm">
- {{ errors.ruleConditions }}
- </p>
- </fieldset>
- <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
- <legend class="px-2 leading-none text-sm">Actions</legend>
- <!-- Loop for actions -->
- <div v-for="(action, key) in editRuleObject.actions" :key="key">
- <!-- AND/OR operator -->
- <div v-if="key !== 0" class="flex justify-center my-2">
- <div class="relative">AND</div>
- </div>
- <div class="p-2 w-full bg-grey-100">
- <div class="flex">
- <div
- class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:items-center w-full"
- >
- <span>Then</span>
- <span class="sm:ml-2">
- <div class="relative">
- <select
- v-model="editRuleObject.actions[key].type"
- @change="ruleActionChange(editRuleObject.actions[key])"
- :id="`rule_action_types_${key}`"
- class="w-full block appearance-none text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option
- v-for="option in actionTypeOptions"
- :key="option.value"
- :value="option.value"
- >
- {{ option.label }}
- </option>
- </select>
- </div>
- </span>
- <span
- v-if="
- editRuleObject.actions[key].type === 'subject' ||
- editRuleObject.actions[key].type === 'displayFrom'
- "
- class="sm:ml-4 flex"
- >
- <div class="flex w-full">
- <input
- v-model="editRuleObject.actions[key].value"
- type="text"
- class="w-full appearance-none bg-white border border-transparent rounded text-grey-700 focus:outline-none p-2"
- :class="errors.ruleActions ? 'border-red-500' : ''"
- placeholder="Enter value"
- autofocus
- />
- </div>
- </span>
- <multiselect
- v-if="editRuleObject.actions[key].type === 'forwardTo'"
- class="sm:!ml-4 flex"
- v-model="editRuleObject.actions[key].value"
- :options="recipientOptions"
- mode="single"
- value-prop="id"
- :close-on-select="true"
- :clear-on-select="false"
- :searchable="false"
- :allow-empty="true"
- placeholder="Select recipient"
- label="email"
- track-by="email"
- >
- </multiselect>
- <span
- v-else-if="editRuleObject.actions[key].type === 'banner'"
- class="sm:ml-4 flex"
- >
- <div class="relative sm:mr-4 w-full">
- <select
- v-model="editRuleObject.actions[key].value"
- :id="`edit_rule_action_banner_${key}`"
- class="w-full block appearance-none sm:w-40 text-grey-700 bg-white p-2 pr-8 rounded shadow focus:ring"
- required
- >
- <option value="top">Top</option>
- <option value="bottom">Bottom</option>
- <option value="off">Off</option>
- </select>
- </div>
- </span>
- </div>
- <div class="flex items-center">
- <!-- delete button -->
- <icon
- v-if="editRuleObject.actions.length > 1"
- name="trash"
- class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
- @click="deleteAction(editRuleObject, key)"
- />
- </div>
- </div>
- </div>
- </div>
- <!-- add action button -->
- <button
- @click="addAction(editRuleObject)"
- class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Add action
- </button>
- <p v-show="errors.ruleActions" class="mt-2 text-red-500 text-sm">
- {{ errors.ruleActions }}
- </p>
- </fieldset>
- <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
- <legend class="px-2 leading-none text-sm">Apply rule on</legend>
- <div class="w-full flex">
- <div class="relative flex items-center">
- <input
- v-model="editRuleObject.forwards"
- id="forwards"
- name="forwards"
- type="checkbox"
- class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-grey-300 rounded"
- />
- <label for="forwards" class="ml-2 text-sm text-grey-700">Forwards</label>
- </div>
- <div class="relative flex items-center mx-4">
- <input
- v-model="editRuleObject.replies"
- id="replies"
- name="replies"
- type="checkbox"
- class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-grey-300 rounded"
- />
- <label for="replies" class="ml-2 text-sm text-grey-700">Replies</label>
- </div>
- <div class="relative flex items-center">
- <input
- v-model="editRuleObject.sends"
- id="sends"
- name="sends"
- type="checkbox"
- class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-grey-300 rounded"
- />
- <label for="sends" class="ml-2 text-sm text-grey-700">Sends</label>
- </div>
- </div>
- </fieldset>
- <div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
- <button
- @click="editRule"
- class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
- :disabled="editRuleLoading"
- >
- Save
- <loader v-if="editRuleLoading" />
- </button>
- <button
- @click="closeEditModal"
- class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Cancel
- </button>
- </div>
- </template>
- </Modal>
- <Modal :open="deleteRuleModalOpen" @close="closeDeleteModal">
- <template v-slot:title> Delete rule </template>
- <template v-slot:content>
- <p class="mt-4 text-grey-700">Are you sure you want to delete this rule?</p>
- <div class="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4">
- <button
- type="button"
- @click="deleteRule(ruleIdToDelete)"
- class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:cursor-not-allowed"
- :disabled="deleteRuleLoading"
- >
- Delete rule
- <loader v-if="deleteRuleLoading" />
- </button>
- <button
- @click="closeDeleteModal"
- class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Cancel
- </button>
- </div>
- </template>
- </Modal>
- <Modal :open="moreInfoOpen" @close="moreInfoOpen = false">
- <template v-slot:title> More information </template>
- <template v-slot:content>
- <p class="mt-4 text-grey-700">
- Rules can be used to perform different actions if certain conditions are met.
- </p>
- <p class="mt-4 text-grey-700">
- For example you could create a rule that checks if the alias is for your custom domain and
- if so then to replace the email subject.
- </p>
- <p class="mt-4 text-grey-700">
- You can choose to apply rules on forwards, replies and/or sends.
- </p>
- <p class="mt-4 text-grey-700">
- Rules are applied in the order displayed on this page from top to bottom. You can re-order
- your rules by dragging them using the icon on the left of each row.
- </p>
- <div class="mt-6 flex flex-col">
- <button
- @click="moreInfoOpen = false"
- class="px-4 py-3 text-grey-800 font-semibold bg-white hover:bg-grey-50 border border-grey-100 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
- >
- Close
- </button>
- </div>
- </template>
- </Modal>
- </div>
- </template>
- <script setup>
- import { onMounted, ref, computed } from 'vue'
- import { Head, Link } from '@inertiajs/vue3'
- import Modal from '../Components/Modal.vue'
- import Toggle from '../Components/Toggle.vue'
- import { roundArrow } from 'tippy.js'
- import tippy from 'tippy.js'
- import draggable from 'vuedraggable'
- import Multiselect from '@vueform/multiselect'
- import { notify } from '@kyvg/vue3-notification'
- import { InformationCircleIcon, FunnelIcon } from '@heroicons/vue/24/outline'
- import { PlusIcon } from '@heroicons/vue/20/solid'
- const props = defineProps({
- initialRows: {
- type: Array,
- required: true,
- },
- recipientOptions: {
- type: Array,
- required: true,
- },
- search: {
- type: String,
- },
- })
- const rows = ref(props.initialRows)
- const editRuleObject = ref({})
- const ruleIdToDelete = ref('')
- const deleteRuleLoading = ref(false)
- const deleteRuleModalOpen = ref(false)
- const createRuleModalOpen = ref(false)
- const editRuleModalOpen = ref(false)
- const moreInfoOpen = ref(false)
- const createRuleLoading = ref(false)
- const editRuleLoading = ref(false)
- const createRuleObject = ref({
- name: '',
- conditions: [
- {
- type: 'select',
- match: 'contains',
- values: [],
- },
- ],
- actions: [
- {
- type: 'select',
- value: '',
- },
- ],
- operator: 'AND',
- forwards: false,
- replies: false,
- sends: false,
- })
- const tippyInstance = ref(null)
- const errors = ref({})
- const conditionTypeOptions = [
- {
- value: 'select',
- label: 'Select',
- },
- {
- value: 'sender',
- label: 'sender email',
- },
- {
- value: 'subject',
- label: 'subject',
- },
- {
- value: 'alias',
- label: 'alias email',
- },
- {
- value: 'alias_description',
- label: 'alias description',
- },
- ]
- const actionTypeOptions = [
- {
- value: 'select',
- label: 'Select',
- },
- {
- value: 'subject',
- label: 'replace the subject with',
- },
- {
- value: 'displayFrom',
- label: 'replace the "from name" with',
- },
- {
- value: 'encryption',
- label: 'turn PGP encryption off',
- },
- {
- value: 'banner',
- label: 'set the banner information location to',
- },
- {
- value: 'block',
- label: 'block the email',
- },
- {
- value: 'removeAttachments',
- label: 'remove attachments',
- },
- {
- value: 'forwardTo',
- label: 'forward to',
- },
- ]
- const indexToHuman = {
- 0: 'first',
- 1: 'second',
- 2: 'third',
- 3: 'forth',
- 4: 'fifth',
- }
- onMounted(() => {
- addTooltips()
- })
- const activeRules = () => {
- return _.filter(rows.value, rule => rule.active)
- }
- const rowsIds = computed(() => {
- return _.map(rows.value, row => row.id)
- })
- const addTooltips = () => {
- if (tippyInstance.value) {
- _.each(tippyInstance.value, instance => instance.destroy())
- }
- tippyInstance.value = tippy('.tooltip', {
- arrow: roundArrow,
- allowHTML: true,
- })
- }
- const debounceToolips = _.debounce(function () {
- addTooltips()
- }, 50)
- const createNewRule = () => {
- errors.value = {}
- if (!createRuleObject.value.name.length) {
- return (errors.value.ruleName = 'Please enter a rule name')
- }
- if (createRuleObject.value.name.length > 50) {
- return (errors.value.ruleName = 'Rule name cannot exceed 50 characters')
- }
- Object.entries(createRuleObject.value.conditions).forEach(([key, condition]) => {
- if (!condition.values.length) {
- return (errors.value.ruleConditions = `You must add some values for the ${indexToHuman[key]} condition, make sure to click "Insert"`)
- }
- })
- if (errors.value.ruleConditions) {
- return
- }
- Object.entries(createRuleObject.value.actions).forEach(([key, action]) => {
- if (!action.value && action.value !== false) {
- return (errors.value.ruleActions = `You must add a value for the ${indexToHuman[key]} action`)
- }
- })
- if (errors.value.ruleActions) {
- return
- }
- createRuleLoading.value = true
- axios
- .post(
- '/api/v1/rules',
- JSON.stringify({
- name: createRuleObject.value.name,
- conditions: createRuleObject.value.conditions,
- actions: createRuleObject.value.actions,
- operator: createRuleObject.value.operator,
- forwards: createRuleObject.value.forwards,
- replies: createRuleObject.value.replies,
- sends: createRuleObject.value.sends,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- },
- )
- .then(({ data }) => {
- createRuleLoading.value = false
- resetCreateRuleObject()
- rows.value.push(data.data)
- createRuleModalOpen.value = false
- debounceToolips()
- reorderRules(false)
- successMessage('New rule created successfully')
- })
- .catch(error => {
- createRuleLoading.value = false
- if (error.response.status === 403) {
- errorMessage(error.response.data)
- } else if (error.response.data) {
- errorMessage(Object.entries(error.response.data.errors)[0][1][0])
- } else {
- errorMessage()
- }
- })
- }
- const editRule = () => {
- errors.value = {}
- if (!editRuleObject.value.name.length) {
- return (errors.value.ruleName = 'Please enter a rule name')
- }
- if (editRuleObject.value.name.length > 50) {
- return (errors.value.ruleName = 'Rule name cannot exceed 50 characters')
- }
- Object.entries(editRuleObject.value.conditions).forEach(([key, condition]) => {
- if (!condition.values.length) {
- return (errors.value.ruleConditions = `You must add some values for the ${indexToHuman[key]} condition, make sure to click "Insert"`)
- }
- })
- if (errors.value.ruleConditions) {
- return
- }
- Object.entries(editRuleObject.value.actions).forEach(([key, action]) => {
- if (!action.value && action.value !== false) {
- return (errors.value.ruleActions = `You must add a value for the ${indexToHuman[key]} action`)
- }
- })
- if (errors.value.ruleActions) {
- return
- }
- editRuleLoading.value = true
- axios
- .patch(
- `/api/v1/rules/${editRuleObject.value.id}`,
- JSON.stringify({
- name: editRuleObject.value.name,
- conditions: editRuleObject.value.conditions,
- actions: editRuleObject.value.actions,
- operator: editRuleObject.value.operator,
- forwards: editRuleObject.value.forwards,
- replies: editRuleObject.value.replies,
- sends: editRuleObject.value.sends,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- },
- )
- .then(response => {
- let rule = _.find(rows.value, ['id', editRuleObject.value.id])
- editRuleLoading.value = false
- rule.name = editRuleObject.value.name
- rule.conditions = editRuleObject.value.conditions
- rule.actions = editRuleObject.value.actions
- rule.operator = editRuleObject.value.operator
- rule.forwards = editRuleObject.value.forwards
- rule.replies = editRuleObject.value.replies
- rule.sends = editRuleObject.value.sends
- closeEditModal()
- successMessage('Rule successfully updated')
- })
- .catch(error => {
- editRuleLoading.value = false
- if (error.response.data) {
- errorMessage(Object.entries(error.response.data.errors)[0][1][0])
- } else {
- errorMessage()
- }
- })
- }
- const deleteRule = id => {
- deleteRuleLoading.value = true
- axios
- .delete(`/api/v1/rules/${id}`)
- .then(response => {
- rows.value = _.reject(rows.value, rule => rule.id === id)
- deleteRuleModalOpen.value = false
- deleteRuleLoading.value = false
- })
- .catch(error => {
- errorMessage()
- deleteRuleModalOpen.value = false
- deleteRuleLoading.value = false
- })
- }
- const activateRule = id => {
- axios
- .post(
- `/api/v1/active-rules`,
- JSON.stringify({
- id: id,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- },
- )
- .then(response => {
- //
- })
- .catch(error => {
- if (error.response !== undefined) {
- errorMessage(error.response.data)
- } else {
- errorMessage()
- }
- })
- }
- const deactivateRule = id => {
- axios
- .delete(`/api/v1/active-rules/${id}`)
- .then(response => {
- //
- })
- .catch(error => {
- if (error.response !== undefined) {
- errorMessage(error.response.data)
- } else {
- errorMessage()
- }
- })
- }
- const reorderRules = (displaySuccess = true) => {
- axios
- .post(
- `/api/v1/reorder-rules`,
- JSON.stringify({
- ids: rowsIds.value,
- }),
- {
- headers: { 'Content-Type': 'application/json' },
- },
- )
- .then(response => {
- if (displaySuccess) {
- successMessage('Rule order successfully updated')
- }
- })
- .catch(error => {
- if (error.response !== undefined) {
- errorMessage(error.response.data)
- } else {
- errorMessage()
- }
- })
- }
- const conditionMatchOptions = (object, key) => {
- if (
- _.includes(['sender', 'subject', 'alias', 'alias_description'], object.conditions[key].type)
- ) {
- return [
- 'contains',
- 'does not contain',
- 'is exactly',
- 'is not',
- 'starts with',
- 'does not start with',
- 'ends with',
- 'does not end with',
- ]
- }
- return []
- }
- const addCondition = object => {
- if (object.conditions.length >= 5) {
- return (errors.value.ruleConditions = `You cannot add more than 5 conditions per rule`)
- }
- object.conditions.push({
- type: 'select',
- match: 'contains',
- values: [],
- })
- }
- const deleteCondition = (object, key) => {
- object.conditions.splice(key, 1)
- }
- const addValueToCondition = (object, key) => {
- if (object.conditions[key].values.length >= 10) {
- return (errors.value.ruleConditions = `You cannot add more than 10 values per condition`)
- }
- if (object.conditions[key].currentConditionValue) {
- object.conditions[key].values.push(object.conditions[key].currentConditionValue)
- }
- // Reset current conditon value input
- object.conditions[key].currentConditionValue = ''
- }
- const addAction = object => {
- if (object.actions.length >= 5) {
- return (errors.value.ruleActions = `You cannot add more than 5 actions per rule`)
- }
- object.actions.push({
- type: 'select',
- value: '',
- })
- }
- const deleteAction = (object, key) => {
- object.actions.splice(key, 1)
- }
- const resetCreateRuleObject = () => {
- createRuleObject.value = {
- name: '',
- conditions: [
- {
- type: 'select',
- match: 'contains',
- values: [],
- },
- ],
- actions: [
- {
- type: 'select',
- value: '',
- },
- ],
- operator: 'AND',
- forwards: false,
- replies: false,
- sends: false,
- }
- }
- const ruleActionChange = action => {
- if (action.type === 'subject' || action.type === 'displayFrom' || action.type === 'select') {
- action.value = ''
- } else if (action.type === 'encryption') {
- action.value = false
- } else if (action.type === 'banner') {
- action.value = 'top'
- } else if (action.type === 'block') {
- action.value = true
- } else if (action.type === 'removeAttachments') {
- action.value = true
- } else if (action.type === 'forwardTo') {
- action.value = ''
- }
- }
- const openCreateModal = () => {
- errors.value = {}
- createRuleModalOpen.value = true
- }
- const openDeleteModal = id => {
- deleteRuleModalOpen.value = true
- ruleIdToDelete.value = id
- }
- const closeDeleteModal = () => {
- deleteRuleModalOpen.value = false
- _.delay(() => (ruleIdToDelete.value = ''), 300)
- }
- const openEditModal = rule => {
- errors.value = {}
- editRuleModalOpen.value = true
- editRuleObject.value = _.cloneDeep(rule)
- }
- const closeEditModal = () => {
- editRuleModalOpen.value = false
- _.delay(() => (editRuleObject.value = {}), 300)
- }
- const successMessage = (text = '') => {
- notify({
- title: 'Success',
- text: text,
- type: 'success',
- })
- }
- const errorMessage = (text = 'An error has occurred, please try again later') => {
- notify({
- title: 'Error',
- text: text,
- type: 'error',
- })
- }
- </script>
- <style>
- .ghost {
- opacity: 0.5;
- background: #c8ebfb;
- }
- </style>
|