Rules.vue 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202
  1. <template>
  2. <div>
  3. <div class="mb-6 flex flex-col md:flex-row justify-between md:items-center">
  4. <div class="flex items-center">
  5. <icon name="move" class="block w-6 h-6 mr-2 text-grey-200 fill-current" />
  6. You can drag and drop rules to order them.
  7. </div>
  8. <button
  9. @click="openCreateModal"
  10. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none ml-auto"
  11. >
  12. Add New Rule
  13. </button>
  14. </div>
  15. <div v-if="initialRules.length" class="bg-white shadow">
  16. <draggable
  17. tag="ul"
  18. v-model="rows"
  19. v-bind="dragOptions"
  20. handle=".handle"
  21. @change="reorderRules"
  22. >
  23. <transition-group type="transition" name="flip-list">
  24. <li
  25. class="relative flex items-center py-3 px-5 border-b border-grey-100"
  26. v-for="row in rows"
  27. :key="row.name"
  28. >
  29. <div class="flex items-center w-3/5">
  30. <icon
  31. name="menu"
  32. class="handle block w-6 h-6 text-grey-200 fill-current cursor-pointer"
  33. />
  34. <span class="m-4">{{ row.name }} </span>
  35. </div>
  36. <div class="w-1/5 relative flex">
  37. <Toggle
  38. v-model="row.active"
  39. @on="activateRule(row.id)"
  40. @off="deactivateRule(row.id)"
  41. />
  42. </div>
  43. <div class="w-1/5 flex justify-end">
  44. <icon
  45. name="edit"
  46. class="block w-6 h-6 mr-3 text-grey-200 fill-current cursor-pointer"
  47. @click.native="openEditModal(row)"
  48. />
  49. <icon
  50. name="trash"
  51. class="block w-6 h-6 text-grey-200 fill-current cursor-pointer"
  52. @click.native="openDeleteModal(row.id)"
  53. />
  54. </div>
  55. </li>
  56. </transition-group>
  57. </draggable>
  58. </div>
  59. <div v-else class="bg-white rounded shadow overflow-x-auto">
  60. <div class="p-8 text-center text-lg text-grey-700">
  61. <h1 class="mb-6 text-2xl text-indigo-800 font-semibold">
  62. It doesn't look like you have any rules yet!
  63. </h1>
  64. <div class="mx-auto mb-6 w-24 border-b-2 border-grey-200"></div>
  65. <p class="mb-4">
  66. Click the button above to create a new rule.
  67. </p>
  68. </div>
  69. </div>
  70. <Modal :open="createRuleModalOpen" @close="createRuleModalOpen = false" :overflow="true">
  71. <div class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6 my-12">
  72. <h2
  73. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  74. >
  75. Create new rule
  76. </h2>
  77. <p class="mt-4 text-grey-700">
  78. Rules work on all emails, including replies and also send froms. New conditions and
  79. actions will be added over time.
  80. </p>
  81. <label for="rule_name" class="block text-grey-700 text-sm my-2">
  82. Name:
  83. </label>
  84. <p v-show="errors.ruleName" class="mb-3 text-red-500 text-sm">
  85. {{ errors.ruleName }}
  86. </p>
  87. <input
  88. v-model="createRuleObject.name"
  89. id="rule_name"
  90. type="text"
  91. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-2"
  92. :class="errors.ruleName ? 'border-red-500' : ''"
  93. placeholder="Enter name"
  94. autofocus
  95. />
  96. <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
  97. <legend class="px-2 leading-none text-sm">Conditions</legend>
  98. <!-- Loop for conditions -->
  99. <div v-for="(condition, key) in createRuleObject.conditions" :key="key">
  100. <!-- AND/OR operator -->
  101. <div v-if="key !== 0" class="flex justify-center my-2">
  102. <div class="relative">
  103. <select
  104. v-model="createRuleObject.operator"
  105. id="rule_operator"
  106. class="block appearance-none w-full text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  107. required
  108. >
  109. <option value="AND">AND </option>
  110. <option value="OR">OR </option>
  111. </select>
  112. <div
  113. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  114. >
  115. <svg
  116. class="fill-current h-4 w-4"
  117. xmlns="http://www.w3.org/2000/svg"
  118. viewBox="0 0 20 20"
  119. >
  120. <path
  121. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  122. />
  123. </svg>
  124. </div>
  125. </div>
  126. </div>
  127. <div class="p-2 w-full bg-grey-100">
  128. <div class="flex">
  129. <div class="flex items-center">
  130. <span>If</span>
  131. <span class="ml-2">
  132. <div class="relative">
  133. <select
  134. v-model="createRuleObject.conditions[key].type"
  135. id="rule_condition_types"
  136. class="block appearance-none w-32 text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  137. required
  138. >
  139. <option
  140. v-for="option in conditionTypeOptions"
  141. :key="option.value"
  142. :value="option.value"
  143. >{{ option.label }}
  144. </option>
  145. </select>
  146. <div
  147. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  148. >
  149. <svg
  150. class="fill-current h-4 w-4"
  151. xmlns="http://www.w3.org/2000/svg"
  152. viewBox="0 0 20 20"
  153. >
  154. <path
  155. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  156. />
  157. </svg>
  158. </div>
  159. </div>
  160. </span>
  161. <span
  162. v-if="conditionMatchOptions(createRuleObject, key).length"
  163. class="ml-4 flex"
  164. >
  165. <div class="relative mr-4">
  166. <select
  167. v-model="createRuleObject.conditions[key].match"
  168. id="rule_condition_matches"
  169. class="block appearance-none w-40 text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  170. required
  171. >
  172. <option
  173. v-for="option in conditionMatchOptions(createRuleObject, key)"
  174. :key="option"
  175. :value="option"
  176. >{{ option }}
  177. </option>
  178. </select>
  179. <div
  180. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  181. >
  182. <svg
  183. class="fill-current h-4 w-4"
  184. xmlns="http://www.w3.org/2000/svg"
  185. viewBox="0 0 20 20"
  186. >
  187. <path
  188. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  189. />
  190. </svg>
  191. </div>
  192. </div>
  193. <div class="flex">
  194. <input
  195. v-model="createRuleObject.conditions[key].currentConditionValue"
  196. @keyup.enter="addValueToCondition(createRuleObject, key)"
  197. type="text"
  198. class="w-full appearance-none bg-white border border-transparent rounded-l text-grey-700 focus:outline-none p-2"
  199. :class="errors.createRuleValues ? 'border-red-500' : ''"
  200. placeholder="Enter value"
  201. autofocus
  202. />
  203. <button class="p-2 bg-grey-200 rounded-r text-grey-600">
  204. <icon
  205. name="check"
  206. class="block w-6 h-6 text-grey-600 fill-current cursor-pointer"
  207. @click.native="addValueToCondition(createRuleObject, key)"
  208. />
  209. </button>
  210. </div>
  211. </span>
  212. </div>
  213. <div class="flex items-center">
  214. <!-- delete button -->
  215. <icon
  216. v-if="createRuleObject.conditions.length > 1"
  217. name="trash"
  218. class="block ml-4 w-6 h-6 text-grey-200 fill-current cursor-pointer"
  219. @click.native="deleteCondition(createRuleObject, key)"
  220. />
  221. </div>
  222. </div>
  223. <div class="mt-2">
  224. <span
  225. v-for="(value, index) in createRuleObject.conditions[key].values"
  226. :key="index"
  227. >
  228. <span class="bg-green-200 text-sm font-semibold rounded-sm pl-1">
  229. {{ value }}
  230. <icon
  231. name="close"
  232. class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
  233. @click.native="createRuleObject.conditions[key].values.splice(index, 1)"
  234. />
  235. </span>
  236. <span
  237. class="mx-1"
  238. v-if="index + 1 !== createRuleObject.conditions[key].values.length"
  239. >
  240. or
  241. </span>
  242. </span>
  243. </div>
  244. </div>
  245. </div>
  246. <!-- add condition button -->
  247. <button
  248. @click="addCondition(createRuleObject)"
  249. class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  250. >
  251. Add condition
  252. </button>
  253. <p v-show="errors.ruleConditions" class="mt-2 text-red-500 text-sm">
  254. {{ errors.ruleConditions }}
  255. </p>
  256. </fieldset>
  257. <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
  258. <legend class="px-2 leading-none text-sm">Actions</legend>
  259. <!-- Loop for actions -->
  260. <div v-for="(action, key) in createRuleObject.actions" :key="key">
  261. <!-- AND/OR operator -->
  262. <div v-if="key !== 0" class="flex justify-center my-2">
  263. <div class="relative">
  264. AND
  265. </div>
  266. </div>
  267. <div class="p-2 w-full bg-grey-100">
  268. <div class="flex">
  269. <div class="flex items-center">
  270. <span>Then</span>
  271. <span class="ml-2">
  272. <div class="relative">
  273. <select
  274. v-model="createRuleObject.actions[key].type"
  275. @change="ruleActionChange(createRuleObject.actions[key])"
  276. id="rule_action_types"
  277. class="block appearance-none text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  278. required
  279. >
  280. <option
  281. v-for="option in actionTypeOptions"
  282. :key="option.value"
  283. :value="option.value"
  284. >{{ option.label }}
  285. </option>
  286. </select>
  287. <div
  288. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  289. >
  290. <svg
  291. class="fill-current h-4 w-4"
  292. xmlns="http://www.w3.org/2000/svg"
  293. viewBox="0 0 20 20"
  294. >
  295. <path
  296. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  297. />
  298. </svg>
  299. </div>
  300. </div>
  301. </span>
  302. <span
  303. v-if="
  304. createRuleObject.actions[key].type === 'subject' ||
  305. createRuleObject.actions[key].type === 'displayFrom'
  306. "
  307. class="ml-4 flex"
  308. >
  309. <div class="flex">
  310. <input
  311. v-model="createRuleObject.actions[key].value"
  312. type="text"
  313. class="w-full appearance-none bg-white border border-transparent rounded text-grey-700 focus:outline-none p-2"
  314. :class="errors.createRuleActionValue ? 'border-red-500' : ''"
  315. placeholder="Enter value"
  316. autofocus
  317. />
  318. </div>
  319. </span>
  320. <span
  321. v-else-if="createRuleObject.actions[key].type === 'banner'"
  322. class="ml-4 flex"
  323. >
  324. <div class="relative mr-4">
  325. <select
  326. v-model="createRuleObject.actions[key].value"
  327. id="create_rule_action_banner"
  328. class="block appearance-none w-40 text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  329. required
  330. >
  331. <option selected value="top">Top </option>
  332. <option selected value="bottom">Bottom </option>
  333. <option selected value="off">Off </option>
  334. </select>
  335. <div
  336. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  337. >
  338. <svg
  339. class="fill-current h-4 w-4"
  340. xmlns="http://www.w3.org/2000/svg"
  341. viewBox="0 0 20 20"
  342. >
  343. <path
  344. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  345. />
  346. </svg>
  347. </div>
  348. </div>
  349. </span>
  350. </div>
  351. <div class="flex items-center">
  352. <!-- delete button -->
  353. <icon
  354. v-if="createRuleObject.actions.length > 1"
  355. name="trash"
  356. class="block ml-4 w-6 h-6 text-grey-200 fill-current cursor-pointer"
  357. @click.native="deleteAction(createRuleObject, key)"
  358. />
  359. </div>
  360. </div>
  361. </div>
  362. </div>
  363. <!-- add action button -->
  364. <button
  365. @click="addAction(createRuleObject)"
  366. class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  367. >
  368. Add action
  369. </button>
  370. <p v-show="errors.ruleActions" class="mt-2 text-red-500 text-sm">
  371. {{ errors.ruleActions }}
  372. </p>
  373. </fieldset>
  374. <div class="mt-6">
  375. <button
  376. @click="createNewRule"
  377. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  378. :class="createRuleLoading ? 'cursor-not-allowed' : ''"
  379. :disabled="createRuleLoading"
  380. >
  381. Create Rule
  382. <loader v-if="createRuleLoading" />
  383. </button>
  384. <button
  385. @click="createRuleModalOpen = false"
  386. 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"
  387. >
  388. Cancel
  389. </button>
  390. </div>
  391. </div>
  392. </Modal>
  393. <Modal :open="editRuleModalOpen" @close="closeEditModal" :overflow="true">
  394. <div class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6 my-12">
  395. <h2
  396. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  397. >
  398. Edit rule
  399. </h2>
  400. <p class="mt-4 text-grey-700">
  401. Rules work on all emails, including replies and also send froms. New conditions and
  402. actions will be added over time.
  403. </p>
  404. <label for="edit_rule_name" class="block text-grey-700 text-sm my-2">
  405. Name:
  406. </label>
  407. <p v-show="errors.ruleName" class="mb-3 text-red-500 text-sm">
  408. {{ errors.ruleName }}
  409. </p>
  410. <input
  411. v-model="editRuleObject.name"
  412. id="edit_rule_name"
  413. type="text"
  414. class="w-full appearance-none bg-grey-100 border border-transparent text-grey-700 focus:outline-none rounded p-2"
  415. :class="errors.ruleName ? 'border-red-500' : ''"
  416. placeholder="Enter name"
  417. autofocus
  418. />
  419. <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
  420. <legend class="px-2 leading-none text-sm">Conditions</legend>
  421. <!-- Loop for conditions -->
  422. <div v-for="(condition, key) in editRuleObject.conditions" :key="key">
  423. <!-- AND/OR operator -->
  424. <div v-if="key !== 0" class="flex justify-center my-2">
  425. <div class="relative">
  426. <select
  427. v-model="editRuleObject.operator"
  428. id="edit_rule_operator"
  429. class="block appearance-none w-full text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  430. required
  431. >
  432. <option value="AND">AND </option>
  433. <option value="OR">OR </option>
  434. </select>
  435. <div
  436. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  437. >
  438. <svg
  439. class="fill-current h-4 w-4"
  440. xmlns="http://www.w3.org/2000/svg"
  441. viewBox="0 0 20 20"
  442. >
  443. <path
  444. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  445. />
  446. </svg>
  447. </div>
  448. </div>
  449. </div>
  450. <div class="p-2 w-full bg-grey-100">
  451. <div class="flex">
  452. <div class="flex items-center">
  453. <span>If</span>
  454. <span class="ml-2">
  455. <div class="relative">
  456. <select
  457. v-model="editRuleObject.conditions[key].type"
  458. id="edit_rule_condition_types"
  459. class="block appearance-none w-32 text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  460. required
  461. >
  462. <option
  463. v-for="option in conditionTypeOptions"
  464. :key="option.value"
  465. :value="option.value"
  466. >{{ option.label }}
  467. </option>
  468. </select>
  469. <div
  470. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  471. >
  472. <svg
  473. class="fill-current h-4 w-4"
  474. xmlns="http://www.w3.org/2000/svg"
  475. viewBox="0 0 20 20"
  476. >
  477. <path
  478. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  479. />
  480. </svg>
  481. </div>
  482. </div>
  483. </span>
  484. <span v-if="conditionMatchOptions(editRuleObject, key).length" class="ml-4 flex">
  485. <div class="relative mr-4">
  486. <select
  487. v-model="editRuleObject.conditions[key].match"
  488. id="edit_rule_condition_matches"
  489. class="block appearance-none w-40 text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  490. required
  491. >
  492. <option
  493. v-for="option in conditionMatchOptions(editRuleObject, key)"
  494. :key="option"
  495. :value="option"
  496. >{{ option }}
  497. </option>
  498. </select>
  499. <div
  500. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  501. >
  502. <svg
  503. class="fill-current h-4 w-4"
  504. xmlns="http://www.w3.org/2000/svg"
  505. viewBox="0 0 20 20"
  506. >
  507. <path
  508. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  509. />
  510. </svg>
  511. </div>
  512. </div>
  513. <div class="flex">
  514. <input
  515. v-model="editRuleObject.conditions[key].currentConditionValue"
  516. @keyup.enter="addValueToCondition(editRuleObect, key)"
  517. type="text"
  518. class="w-full appearance-none bg-white border border-transparent rounded-l text-grey-700 focus:outline-none p-2"
  519. :class="errors.ruleConditions ? 'border-red-500' : ''"
  520. placeholder="Enter value"
  521. autofocus
  522. />
  523. <button class="p-2 bg-grey-200 rounded-r text-grey-600">
  524. <icon
  525. name="check"
  526. class="block w-6 h-6 text-grey-600 fill-current cursor-pointer"
  527. @click.native="addValueToCondition(editRuleObject, key)"
  528. />
  529. </button>
  530. </div>
  531. </span>
  532. </div>
  533. <div class="flex items-center">
  534. <!-- delete button -->
  535. <icon
  536. v-if="editRuleObject.conditions.length > 1"
  537. name="trash"
  538. class="block ml-4 w-6 h-6 text-grey-200 fill-current cursor-pointer"
  539. @click.native="deleteCondition(editRuleObject, key)"
  540. />
  541. </div>
  542. </div>
  543. <div class="mt-2">
  544. <span v-for="(value, index) in editRuleObject.conditions[key].values" :key="index">
  545. <span class="bg-green-200 text-sm font-semibold rounded-sm pl-1">
  546. {{ value }}
  547. <icon
  548. name="close"
  549. class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
  550. @click.native="editRuleObject.conditions[key].values.splice(index, 1)"
  551. />
  552. </span>
  553. <span
  554. class="mx-1"
  555. v-if="index + 1 !== editRuleObject.conditions[key].values.length"
  556. >
  557. or
  558. </span>
  559. </span>
  560. </div>
  561. </div>
  562. </div>
  563. <!-- add condition button -->
  564. <button
  565. @click="addCondition(editRuleObject)"
  566. class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  567. >
  568. Add condition
  569. </button>
  570. <p v-show="errors.ruleConditions" class="mt-2 text-red-500 text-sm">
  571. {{ errors.ruleConditions }}
  572. </p>
  573. </fieldset>
  574. <fieldset class="border border-cyan-400 p-4 my-4 rounded-sm">
  575. <legend class="px-2 leading-none text-sm">Actions</legend>
  576. <!-- Loop for actions -->
  577. <div v-for="(action, key) in editRuleObject.actions" :key="key">
  578. <!-- AND/OR operator -->
  579. <div v-if="key !== 0" class="flex justify-center my-2">
  580. <div class="relative">
  581. AND
  582. </div>
  583. </div>
  584. <div class="p-2 w-full bg-grey-100">
  585. <div class="flex">
  586. <div class="flex items-center">
  587. <span>Then</span>
  588. <span class="ml-2">
  589. <div class="relative">
  590. <select
  591. v-model="editRuleObject.actions[key].type"
  592. @change="ruleActionChange(editRuleObject.actions[key])"
  593. id="rule_action_types"
  594. class="block appearance-none text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  595. required
  596. >
  597. <option
  598. v-for="option in actionTypeOptions"
  599. :key="option.value"
  600. :value="option.value"
  601. >{{ option.label }}
  602. </option>
  603. </select>
  604. <div
  605. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  606. >
  607. <svg
  608. class="fill-current h-4 w-4"
  609. xmlns="http://www.w3.org/2000/svg"
  610. viewBox="0 0 20 20"
  611. >
  612. <path
  613. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  614. />
  615. </svg>
  616. </div>
  617. </div>
  618. </span>
  619. <span
  620. v-if="
  621. editRuleObject.actions[key].type === 'subject' ||
  622. editRuleObject.actions[key].type === 'displayFrom'
  623. "
  624. class="ml-4 flex"
  625. >
  626. <div class="flex">
  627. <input
  628. v-model="editRuleObject.actions[key].value"
  629. type="text"
  630. class="w-full appearance-none bg-white border border-transparent rounded text-grey-700 focus:outline-none p-2"
  631. :class="errors.ruleActions ? 'border-red-500' : ''"
  632. placeholder="Enter value"
  633. autofocus
  634. />
  635. </div>
  636. </span>
  637. <span v-else-if="editRuleObject.actions[key].type === 'banner'" class="ml-4 flex">
  638. <div class="relative mr-4">
  639. <select
  640. v-model="editRuleObject.actions[key].value"
  641. id="edit_rule_action_banner"
  642. class="block appearance-none w-40 text-grey-700 bg-white p-2 pr-6 rounded shadow focus:shadow-outline"
  643. required
  644. >
  645. <option value="top">Top </option>
  646. <option value="bottom">Bottom </option>
  647. <option value="off">Off </option>
  648. </select>
  649. <div
  650. class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"
  651. >
  652. <svg
  653. class="fill-current h-4 w-4"
  654. xmlns="http://www.w3.org/2000/svg"
  655. viewBox="0 0 20 20"
  656. >
  657. <path
  658. d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
  659. />
  660. </svg>
  661. </div>
  662. </div>
  663. </span>
  664. </div>
  665. <div class="flex items-center">
  666. <!-- delete button -->
  667. <icon
  668. v-if="editRuleObject.actions.length > 1"
  669. name="trash"
  670. class="block ml-4 w-6 h-6 text-grey-200 fill-current cursor-pointer"
  671. @click.native="deleteAction(editRuleObject, key)"
  672. />
  673. </div>
  674. </div>
  675. </div>
  676. </div>
  677. <!-- add action button -->
  678. <button
  679. @click="addAction(editRuleObject)"
  680. class="mt-4 p-2 text-grey-800 bg-white hover:bg-grey-50 border border-grey-100 rounded focus:outline-none"
  681. >
  682. Add action
  683. </button>
  684. <p v-show="errors.ruleActions" class="mt-2 text-red-500 text-sm">
  685. {{ errors.ruleActions }}
  686. </p>
  687. </fieldset>
  688. <div class="mt-6">
  689. <button
  690. @click="editRule"
  691. class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
  692. :class="editRuleLoading ? 'cursor-not-allowed' : ''"
  693. :disabled="editRuleLoading"
  694. >
  695. Edit Rule
  696. <loader v-if="editRuleLoading" />
  697. </button>
  698. <button
  699. @click="closeEditModal"
  700. 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"
  701. >
  702. Cancel
  703. </button>
  704. </div>
  705. </div>
  706. </Modal>
  707. <Modal :open="deleteRuleModalOpen" @close="closeDeleteModal">
  708. <div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
  709. <h2
  710. class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
  711. >
  712. Delete rule
  713. </h2>
  714. <p class="mt-4 text-grey-700">
  715. Are you sure you want to delete this rule?
  716. </p>
  717. <div class="mt-6">
  718. <button
  719. type="button"
  720. @click="deleteRule(ruleIdToDelete)"
  721. class="px-4 py-3 text-white font-semibold bg-red-500 hover:bg-red-600 border border-transparent rounded focus:outline-none"
  722. :class="deleteRuleLoading ? 'cursor-not-allowed' : ''"
  723. :disabled="deleteRuleLoading"
  724. >
  725. Delete rule
  726. <loader v-if="deleteRuleLoading" />
  727. </button>
  728. <button
  729. @click="closeDeleteModal"
  730. 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"
  731. >
  732. Cancel
  733. </button>
  734. </div>
  735. </div>
  736. </Modal>
  737. </div>
  738. </template>
  739. <script>
  740. import Modal from './../components/Modal.vue'
  741. import Toggle from './../components/Toggle.vue'
  742. import tippy from 'tippy.js'
  743. import draggable from 'vuedraggable'
  744. export default {
  745. props: {
  746. initialRules: {
  747. type: Array,
  748. required: true,
  749. },
  750. },
  751. components: {
  752. Modal,
  753. Toggle,
  754. draggable,
  755. },
  756. mounted() {
  757. this.addTooltips()
  758. },
  759. data() {
  760. return {
  761. editRuleObject: {},
  762. ruleIdToDelete: '',
  763. deleteRuleLoading: false,
  764. deleteRuleModalOpen: false,
  765. createRuleModalOpen: false,
  766. editRuleModalOpen: false,
  767. createRuleLoading: false,
  768. editRuleLoading: false,
  769. createRuleObject: {
  770. name: '',
  771. conditions: [
  772. {
  773. type: 'select',
  774. match: 'contains',
  775. values: [],
  776. },
  777. ],
  778. actions: [
  779. {
  780. type: 'select',
  781. value: '',
  782. },
  783. ],
  784. operator: 'AND',
  785. },
  786. rows: this.initialRules,
  787. conditionTypeOptions: [
  788. {
  789. value: 'select',
  790. label: 'Select',
  791. },
  792. {
  793. value: 'sender',
  794. label: 'the sender',
  795. },
  796. {
  797. value: 'subject',
  798. label: 'the subject',
  799. },
  800. {
  801. value: 'alias',
  802. label: 'the alias',
  803. },
  804. ],
  805. actionTypeOptions: [
  806. {
  807. value: 'select',
  808. label: 'Select',
  809. },
  810. {
  811. value: 'subject',
  812. label: 'replace the subject with',
  813. },
  814. {
  815. value: 'displayFrom',
  816. label: 'replace the "from name" with',
  817. },
  818. {
  819. value: 'encryption',
  820. label: 'turn PGP encryption off',
  821. },
  822. {
  823. value: 'banner',
  824. label: 'set the banner information location to',
  825. },
  826. {
  827. value: 'block',
  828. label: 'block the email',
  829. },
  830. ],
  831. errors: {},
  832. }
  833. },
  834. watch: {
  835. editRuleObject: _.debounce(function() {
  836. this.addTooltips()
  837. }, 50),
  838. },
  839. computed: {
  840. activeRules() {
  841. return _.filter(this.rows, rule => rule.active)
  842. },
  843. dragOptions() {
  844. return {
  845. animation: 0,
  846. group: 'description',
  847. disabled: false,
  848. ghostClass: 'ghost',
  849. }
  850. },
  851. rowsIds() {
  852. return _.map(this.rows, row => row.id)
  853. },
  854. },
  855. methods: {
  856. addTooltips() {
  857. tippy('.tooltip', {
  858. arrow: true,
  859. arrowType: 'round',
  860. })
  861. },
  862. debounceToolips: _.debounce(function() {
  863. this.addTooltips()
  864. }, 50),
  865. openCreateModal() {
  866. this.errors = {}
  867. this.createRuleModalOpen = true
  868. },
  869. openDeleteModal(id) {
  870. this.deleteRuleModalOpen = true
  871. this.ruleIdToDelete = id
  872. },
  873. closeDeleteModal() {
  874. this.deleteRuleModalOpen = false
  875. this.ruleIdToDelete = ''
  876. },
  877. openEditModal(rule) {
  878. this.errors = {}
  879. this.editRuleModalOpen = true
  880. this.editRuleObject = _.cloneDeep(rule)
  881. },
  882. closeEditModal() {
  883. this.editRuleModalOpen = false
  884. this.editRuleObject = {}
  885. },
  886. deleteRule(id) {
  887. this.deleteRuleLoading = true
  888. axios
  889. .delete(`/api/v1/rules/${id}`)
  890. .then(response => {
  891. this.rows = _.reject(this.rows, rule => rule.id === id)
  892. this.deleteRuleModalOpen = false
  893. this.deleteRuleLoading = false
  894. })
  895. .catch(error => {
  896. this.error()
  897. this.deleteRuleModalOpen = false
  898. this.deleteRuleLoading = false
  899. })
  900. },
  901. createNewRule() {
  902. this.errors = {}
  903. if (!this.createRuleObject.name.length) {
  904. return (this.errors.ruleName = 'Please enter a rule name')
  905. }
  906. if (this.createRuleObject.name.length > 50) {
  907. return (this.errors.ruleName = 'Rule name cannot exceed 50 characters')
  908. }
  909. if (!this.createRuleObject.conditions[0].values.length) {
  910. return (this.errors.ruleConditions = 'You must add some values for the condition')
  911. }
  912. if (
  913. !this.createRuleObject.actions[0].value &&
  914. this.createRuleObject.actions[0].value !== false
  915. ) {
  916. return (this.errors.ruleActions = 'You must add a value for the action')
  917. }
  918. this.createRuleLoading = true
  919. axios
  920. .post(
  921. '/api/v1/rules',
  922. JSON.stringify({
  923. name: this.createRuleObject.name,
  924. conditions: this.createRuleObject.conditions,
  925. actions: this.createRuleObject.actions,
  926. operator: this.createRuleObject.operator,
  927. }),
  928. {
  929. headers: { 'Content-Type': 'application/json' },
  930. }
  931. )
  932. .then(({ data }) => {
  933. this.createRuleLoading = false
  934. this.resetCreateRuleObject()
  935. this.rows.push(data.data)
  936. this.createRuleModalOpen = false
  937. this.success('New rule created successfully')
  938. })
  939. .catch(error => {
  940. this.createRuleLoading = false
  941. this.error()
  942. })
  943. },
  944. editRule() {
  945. this.errors = {}
  946. if (!this.editRuleObject.name.length) {
  947. return (this.errors.ruleName = 'Please enter a rule name')
  948. }
  949. if (this.editRuleObject.name.length > 50) {
  950. return (this.errors.ruleName = 'Rule name cannot exceed 50 characters')
  951. }
  952. if (!this.editRuleObject.conditions[0].values.length) {
  953. return (this.errors.ruleConditions = 'You must add some values for the condition')
  954. }
  955. if (!this.editRuleObject.actions[0].value && this.editRuleObject.actions[0].value !== false) {
  956. return (this.errors.ruleActions = 'You must add a value for the action')
  957. }
  958. this.editRuleLoading = true
  959. axios
  960. .patch(
  961. `/api/v1/rules/${this.editRuleObject.id}`,
  962. JSON.stringify({
  963. name: this.editRuleObject.name,
  964. conditions: this.editRuleObject.conditions,
  965. actions: this.editRuleObject.actions,
  966. operator: this.editRuleObject.operator,
  967. }),
  968. {
  969. headers: { 'Content-Type': 'application/json' },
  970. }
  971. )
  972. .then(response => {
  973. let rule = _.find(this.rows, ['id', this.editRuleObject.id])
  974. this.editRuleLoading = false
  975. rule.name = this.editRuleObject.name
  976. rule.conditions = this.editRuleObject.conditions
  977. rule.actions = this.editRuleObject.actions
  978. rule.operator = this.editRuleObject.operator
  979. this.editRuleObject = {}
  980. this.editRuleModalOpen = false
  981. this.success('Rule successfully updated')
  982. })
  983. .catch(error => {
  984. this.editRuleLoading = false
  985. if (error.response.data) {
  986. this.error(Object.entries(error.response.data.errors)[0][1][0])
  987. } else {
  988. this.error()
  989. }
  990. })
  991. },
  992. activateRule(id) {
  993. axios
  994. .post(
  995. `/api/v1/active-rules`,
  996. JSON.stringify({
  997. id: id,
  998. }),
  999. {
  1000. headers: { 'Content-Type': 'application/json' },
  1001. }
  1002. )
  1003. .then(response => {
  1004. //
  1005. })
  1006. .catch(error => {
  1007. if (error.response !== undefined) {
  1008. this.error(error.response.data)
  1009. } else {
  1010. this.error()
  1011. }
  1012. })
  1013. },
  1014. deactivateRule(id) {
  1015. axios
  1016. .delete(`/api/v1/active-rules/${id}`)
  1017. .then(response => {
  1018. //
  1019. })
  1020. .catch(error => {
  1021. if (error.response !== undefined) {
  1022. this.error(error.response.data)
  1023. } else {
  1024. this.error()
  1025. }
  1026. })
  1027. },
  1028. reorderRules() {
  1029. axios
  1030. .post(
  1031. `/api/v1/reorder-rules`,
  1032. JSON.stringify({
  1033. ids: this.rowsIds,
  1034. }),
  1035. {
  1036. headers: { 'Content-Type': 'application/json' },
  1037. }
  1038. )
  1039. .then(response => {
  1040. this.success('Rule order successfully updated')
  1041. })
  1042. .catch(error => {
  1043. if (error.response !== undefined) {
  1044. this.error(error.response.data)
  1045. } else {
  1046. this.error()
  1047. }
  1048. })
  1049. },
  1050. conditionMatchOptions(object, key) {
  1051. if (_.includes(['sender', 'subject', 'alias'], object.conditions[key].type)) {
  1052. return [
  1053. 'contains',
  1054. 'does not contain',
  1055. 'is exactly',
  1056. 'is not',
  1057. 'starts with',
  1058. 'does not start with',
  1059. 'ends with',
  1060. 'does not end with',
  1061. ]
  1062. }
  1063. return []
  1064. },
  1065. addCondition(object) {
  1066. object.conditions.push({
  1067. type: 'select',
  1068. match: 'contains',
  1069. values: [],
  1070. })
  1071. },
  1072. deleteCondition(object, key) {
  1073. object.conditions.splice(key, 1)
  1074. },
  1075. addValueToCondition(object, key) {
  1076. if (object.conditions[key].currentConditionValue) {
  1077. object.conditions[key].values.push(object.conditions[key].currentConditionValue)
  1078. }
  1079. // Reset current conditon value input
  1080. object.conditions[key].currentConditionValue = ''
  1081. },
  1082. addAction(object) {
  1083. object.actions.push({
  1084. type: 'select',
  1085. value: '',
  1086. })
  1087. },
  1088. deleteAction(object, key) {
  1089. object.actions.splice(key, 1)
  1090. },
  1091. resetCreateRuleObject() {
  1092. this.createRuleObject = {
  1093. name: '',
  1094. conditions: [
  1095. {
  1096. type: 'select',
  1097. match: 'contains',
  1098. values: [],
  1099. },
  1100. ],
  1101. actions: [
  1102. {
  1103. type: 'select',
  1104. value: '',
  1105. },
  1106. ],
  1107. operator: 'AND',
  1108. }
  1109. },
  1110. ruleActionChange(action) {
  1111. if (action.type === 'subject' || action.type === 'displayFrom' || action.type === 'select') {
  1112. action.value = ''
  1113. } else if (action.type === 'encryption') {
  1114. action.value = false
  1115. } else if (action.type === 'banner') {
  1116. action.value = 'top'
  1117. } else if (action.type === 'block') {
  1118. action.value = true
  1119. }
  1120. },
  1121. success(text = '') {
  1122. this.$notify({
  1123. title: 'Success',
  1124. text: text,
  1125. type: 'success',
  1126. })
  1127. },
  1128. error(text = 'An error has occurred, please try again later') {
  1129. this.$notify({
  1130. title: 'Error',
  1131. text: text,
  1132. type: 'error',
  1133. })
  1134. },
  1135. },
  1136. }
  1137. </script>
  1138. <style>
  1139. .flip-list-move {
  1140. transition: transform 0.5s;
  1141. }
  1142. .ghost {
  1143. opacity: 0.5;
  1144. background: #c8ebfb;
  1145. }
  1146. </style>