SolutionCard.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <template>
  2. <div>
  3. <div
  4. class="solution-toggle"
  5. @click="toggleSolutions"
  6. :class="{
  7. 'solution-toggle-show': isHidingSolutions,
  8. }"
  9. >
  10. <a v-if="isHidingSolutions" class="link-solution" target="_blank"
  11. ><Icon name="lightbulb" class="text-xs mr-1" /> Show solutions</a
  12. >
  13. <a v-else class="link-solution" target="_blank">Hide solutions</a>
  14. </div>
  15. <div
  16. ref="solutionCard"
  17. class="solution"
  18. :class="{
  19. 'solution-hidden': isHidingSolutions,
  20. }"
  21. >
  22. <div class="solution-main">
  23. <div class="solution-background mx-0">
  24. <svg
  25. class="hidden absolute right-0 h-full | md:block"
  26. x="0px"
  27. y="0px"
  28. viewBox="0 0 299 452"
  29. >
  30. <g style="opacity: 0.075">
  31. <polygon
  32. style="fill:rgb(63,63,63)"
  33. points="298.1,451.9 150.9,451.9 21,226.9 298.1,227.1"
  34. />
  35. <polygon
  36. style="fill:rgb(151,151,151)"
  37. points="298.1,227.1 21,226.9 150.9,1.9 298.1,1.9"
  38. />
  39. </g>
  40. </svg>
  41. </div>
  42. <div class="solution-content-wrapper scrollbar">
  43. <div class="solution-content ml-0">
  44. <h2 v-if="solution.title !== ''" class="solution-title">
  45. {{ solution.title }}
  46. </h2>
  47. <div
  48. v-if="solution.description"
  49. v-html="markdown(solution.description)"
  50. class="solution-description"
  51. ></div>
  52. <div v-if="solution.is_runnable">
  53. <p
  54. v-html="markdown(solution.action_description)"
  55. class="solution-description"
  56. ></p>
  57. <p v-if="canExecuteSolutions === null" class="py-4 text-sm italic">
  58. Loading...
  59. </p>
  60. <div class="mt-4">
  61. <button
  62. v-if="
  63. solution.is_runnable &&
  64. canExecuteSolutions === true &&
  65. executionSuccessful === null
  66. "
  67. :disabled="runningSolution"
  68. @click="execute"
  69. class="button-secondary button-lg bg-tint-300 hover:bg-tint-400"
  70. >
  71. <span v-if="runningSolution">Running...</span>
  72. <span v-if="!runningSolution">{{
  73. solution.run_button_text
  74. }}</span>
  75. </button>
  76. <p v-if="executionSuccessful">
  77. <strong class="font-semibold"
  78. >The solution was executed successfully.</strong
  79. >
  80. <a href="#" @click.prevent="refresh" class="link-solution"
  81. >Refresh now.</a
  82. >
  83. </p>
  84. <p v-if="executionSuccessful === false">
  85. Something went wrong when executing the solution. Please try
  86. refreshing the page and try again.
  87. </p>
  88. </div>
  89. </div>
  90. <div
  91. class="mt-8 grid justify-start"
  92. v-if="Object.entries(solution.links).length > 0"
  93. >
  94. <div class="border-t-2 border-gray-700 opacity-25 " />
  95. <div class="pt-2 grid cols-auto-1fr gapx-4 gapy-2 text-sm">
  96. <label class="font-semibold uppercase tracking-wider"
  97. >Read more</label
  98. >
  99. <ul>
  100. <li v-for="(link, label) in solution.links" :key="label">
  101. <a :href="link" class="link-solution" target="_blank">{{
  102. label
  103. }}</a>
  104. </li>
  105. </ul>
  106. </div>
  107. </div>
  108. </div>
  109. </div>
  110. </div>
  111. </div>
  112. </div>
  113. </template>
  114. <script>
  115. const MarkdownIt = require('markdown-it')();
  116. const cookieName = 'hide_solutions';
  117. let animationTimeout = null;
  118. export default {
  119. inject: ['config'],
  120. props: {
  121. solution: { required: true },
  122. },
  123. data() {
  124. return {
  125. isHidingSolutions: this.hasHideSolutionsCookie(),
  126. canExecuteSolutions: null,
  127. runningSolution: false,
  128. executionSuccessful: null,
  129. };
  130. },
  131. computed: {
  132. healthCheckEndpoint() {
  133. return this.solution.execute_endpoint.replace('execute-solution', 'health-check');
  134. },
  135. },
  136. created() {
  137. this.configureRunnableSolutions();
  138. },
  139. mounted() {
  140. if (this.isHidingSolutions) {
  141. this.$refs.solutionCard.classList.add('solution-hidden');
  142. }
  143. },
  144. methods: {
  145. configureRunnableSolutions() {
  146. if (!this.config.enableRunnableSolutions) {
  147. this.canExecuteSolutions = false;
  148. return;
  149. }
  150. this.checkExecutionEndpoint();
  151. },
  152. markdown(string) {
  153. return MarkdownIt.render(string);
  154. },
  155. async checkExecutionEndpoint() {
  156. try {
  157. const healthCheck = await (await fetch(this.healthCheckEndpoint)).json();
  158. this.canExecuteSolutions = healthCheck.can_execute_commands;
  159. } catch (error) {
  160. this.canExecuteSolutions = false;
  161. }
  162. },
  163. async execute() {
  164. if (this.runningSolution) {
  165. return;
  166. }
  167. try {
  168. this.runningSolution = true;
  169. const response = await fetch(this.solution.execute_endpoint, {
  170. method: 'POST',
  171. headers: {
  172. 'Content-Type': 'application/json',
  173. Accept: 'application/json',
  174. },
  175. body: JSON.stringify({
  176. solution: this.solution.class,
  177. parameters: this.solution.run_parameters,
  178. }),
  179. });
  180. this.executionSuccessful = response.status === 200;
  181. } catch (error) {
  182. console.error(error);
  183. this.executionSuccessful = false;
  184. } finally {
  185. this.runningSolution = false;
  186. }
  187. },
  188. refresh() {
  189. location.reload();
  190. },
  191. getUrlLabel(url) {
  192. const tempElement = document.createElement('a');
  193. tempElement.href = url;
  194. return tempElement.hostname;
  195. },
  196. toggleSolutions() {
  197. if (!this.isHidingSolutions) {
  198. this.$refs.solutionCard.classList.add('solution-hiding');
  199. animationTimeout = window.setTimeout(() => {
  200. this.$refs.solutionCard.classList.remove('solution-hiding');
  201. this.toggleHidingSolutions();
  202. }, 100);
  203. } else {
  204. window.clearTimeout(animationTimeout);
  205. this.toggleHidingSolutions();
  206. }
  207. },
  208. toggleHidingSolutions() {
  209. if (this.isHidingSolutions) {
  210. document.cookie = `${cookieName}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
  211. this.isHidingSolutions = false;
  212. return;
  213. }
  214. const expires = new Date();
  215. expires.setTime(expires.getTime() + 365 * 24 * 60 * 60 * 1000);
  216. document.cookie = `${cookieName}=true;expires=${expires.toUTCString()};path=/;`;
  217. this.isHidingSolutions = true;
  218. },
  219. hasHideSolutionsCookie() {
  220. return document.cookie.includes(cookieName);
  221. },
  222. },
  223. };
  224. </script>