AdminExtensions.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <template lang='pug'>
  2. q-page.admin-extensions
  3. .row.q-pa-md.items-center
  4. .col-auto
  5. img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-module.svg')
  6. .col.q-pl-md
  7. .text-h5.text-primary.animated.fadeInLeft {{ t('admin.extensions.title') }}
  8. .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.extensions.subtitle') }}
  9. .col-auto
  10. q-btn.acrylic-btn.q-mr-sm(
  11. icon='las la-question-circle'
  12. flat
  13. color='grey'
  14. :aria-label='t(`common.actions.viewDocs`)'
  15. :href='siteStore.docsBase + `/system/extensions`'
  16. target='_blank'
  17. type='a'
  18. )
  19. q-tooltip {{ t(`common.actions.viewDocs`) }}
  20. q-btn.acrylic-btn(
  21. icon='las la-redo-alt'
  22. flat
  23. color='secondary'
  24. :loading='state.loading > 0'
  25. :aria-label='t(`common.actions.refresh`)'
  26. @click='load'
  27. )
  28. q-tooltip {{ t(`common.actions.refresh`) }}
  29. q-separator(inset)
  30. .row.q-pa-md.q-col-gutter-md
  31. .col-12
  32. q-card
  33. q-list(separator)
  34. q-item(
  35. v-for='ext of state.extensions'
  36. :key='`ext-` + ext.key'
  37. )
  38. blueprint-icon(icon='module')
  39. q-item-section
  40. q-item-label {{ext.title}}
  41. q-item-label(caption) {{ext.description}}
  42. q-item-section(side)
  43. .row
  44. q-btn-group(unelevated)
  45. q-btn(
  46. icon='las la-check'
  47. size='sm'
  48. color='positive'
  49. padding='xs sm'
  50. v-if='ext.isInstalled'
  51. :ripple='false'
  52. )
  53. q-tooltip(
  54. anchor='center left'
  55. self='center right'
  56. ) {{t('admin.extensions.installed')}}
  57. q-btn(
  58. :label='t(`admin.extensions.install`)'
  59. color='blue-7'
  60. v-if='ext.isCompatible && !ext.isInstalled && ext.isInstallable'
  61. @click='install(ext)'
  62. no-caps
  63. )
  64. q-btn(
  65. v-else-if='ext.isCompatible && ext.isInstalled && ext.isInstallable'
  66. :label='t(`admin.extensions.reinstall`)'
  67. color='blue-7'
  68. @click='install(ext)'
  69. no-caps
  70. )
  71. q-btn(
  72. v-else-if='ext.isCompatible && ext.isInstalled && !ext.isInstallable'
  73. :label='t(`admin.extensions.installed`)'
  74. color='positive'
  75. no-caps
  76. :ripple='false'
  77. )
  78. q-btn(
  79. v-else-if='ext.isCompatible'
  80. :label='t(`admin.extensions.instructions`)'
  81. icon='las la-info-circle'
  82. color='indigo'
  83. outline
  84. type='a'
  85. :href='`https://docs.js.wiki/admin/extensions/` + ext.key'
  86. target='_blank'
  87. no-caps
  88. )
  89. q-tooltip(
  90. anchor='center left'
  91. self='center right'
  92. ) {{t('admin.extensions.instructionsHint')}}
  93. q-btn(
  94. v-else
  95. color='negative'
  96. outline
  97. :label='t(`admin.extensions.incompatible`)'
  98. no-caps
  99. :ripple='false'
  100. )
  101. </template>
  102. <script setup>
  103. import gql from 'graphql-tag'
  104. import { cloneDeep } from 'lodash-es'
  105. import { useI18n } from 'vue-i18n'
  106. import { useMeta, useQuasar } from 'quasar'
  107. import { computed, onMounted, reactive, watch } from 'vue'
  108. import { useAdminStore } from '@/stores/admin'
  109. import { useSiteStore } from '@/stores/site'
  110. // QUASAR
  111. const $q = useQuasar()
  112. // STORES
  113. const adminStore = useAdminStore()
  114. const siteStore = useSiteStore()
  115. // I18N
  116. const { t } = useI18n()
  117. // META
  118. useMeta({
  119. title: t('admin.extensions.title')
  120. })
  121. // DATA
  122. const state = reactive({
  123. loading: false,
  124. extensions: []
  125. })
  126. // METHODS
  127. async function load () {
  128. state.loading++
  129. $q.loading.show()
  130. const resp = await APOLLO_CLIENT.query({
  131. query: gql`
  132. query fetchExtensions {
  133. systemExtensions {
  134. key
  135. title
  136. description
  137. isInstalled
  138. isInstallable
  139. isCompatible
  140. }
  141. }
  142. `,
  143. fetchPolicy: 'network-only'
  144. })
  145. state.extensions = cloneDeep(resp?.data?.systemExtensions)
  146. $q.loading.hide()
  147. state.loading--
  148. }
  149. async function install (ext) {
  150. $q.loading.show({
  151. message: t('admin.extensions.installing') + '<br>' + t('admin.extensions.installingHint'),
  152. html: true
  153. })
  154. try {
  155. const respRaw = await APOLLO_CLIENT.mutate({
  156. mutation: gql`
  157. mutation installExtension (
  158. $key: String!
  159. ) {
  160. installExtension (
  161. key: $key
  162. ) {
  163. operation {
  164. succeeded
  165. message
  166. }
  167. }
  168. }
  169. `,
  170. variables: {
  171. key: ext.key
  172. }
  173. })
  174. if (respRaw.data?.installExtension?.operation?.succeeded) {
  175. $q.notify({
  176. type: 'positive',
  177. message: t('admin.extensions.installSuccess')
  178. })
  179. ext.isInstalled = true
  180. // this.$forceUpdate()
  181. } else {
  182. throw new Error(respRaw.data?.installExtension?.operation?.message || 'An unexpected error occured')
  183. }
  184. } catch (err) {
  185. $q.notify({
  186. type: 'negative',
  187. message: t('admin.extensions.installFailed'),
  188. caption: err.message
  189. })
  190. }
  191. $q.loading.hide()
  192. }
  193. // MOUNTED
  194. onMounted(() => {
  195. load()
  196. })
  197. </script>
  198. <style lang='scss'>
  199. .admin-extensions {
  200. .q-expansion-item__content .q-card {
  201. @at-root .body--light & {
  202. background-color: $grey-1;
  203. }
  204. @at-root .body--dark & {
  205. background-color: $dark-3;
  206. }
  207. }
  208. }
  209. </style>