Rules.vue 49 KB

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