Rules.vue 45 KB

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