PersonalSettingNewAccessTokenFlyout.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, ref } from 'vue'
  4. import Form from '#shared/components/Form/Form.vue'
  5. import type { FormSubmitData } from '#shared/components/Form/types.ts'
  6. import { useForm } from '#shared/components/Form/useForm.ts'
  7. import { useUserCurrentAccessTokenAddMutation } from '#shared/entities/user/current/graphql/mutations/userCurrentAccessTokenAdd.api.ts'
  8. import { UserCurrentAccessTokenListDocument } from '#shared/entities/user/current/graphql/queries/userCurrentAcessTokenList.api.ts'
  9. import { defineFormSchema } from '#shared/form/defineFormSchema.ts'
  10. import {
  11. EnumFormUpdaterId,
  12. type UserCurrentAccessTokenListQuery,
  13. } from '#shared/graphql/types.ts'
  14. import MutationHandler from '#shared/server/apollo/handler/MutationHandler.ts'
  15. import CommonFlyout from '#desktop/components/CommonFlyout/CommonFlyout.vue'
  16. import type { ActionFooterOptions } from '#desktop/components/CommonFlyout/types.ts'
  17. import { closeFlyout } from '#desktop/components/CommonFlyout/useFlyout.ts'
  18. import CommonInputCopyToClipboard from '#desktop/components/CommonInputCopyToClipboard/CommonInputCopyToClipboard.vue'
  19. import type { NewTokenAccessFormData } from '../types/token-access.ts'
  20. const { form } = useForm()
  21. const formSchema = defineFormSchema([
  22. {
  23. type: 'text',
  24. name: 'name',
  25. label: __('Name'),
  26. required: true,
  27. },
  28. {
  29. type: 'date',
  30. name: 'expires_at',
  31. label: __('Expiration date'),
  32. props: {
  33. futureOnly: true,
  34. },
  35. },
  36. {
  37. type: 'permissions',
  38. name: 'permissions',
  39. label: 'Permissions',
  40. props: {
  41. options: [],
  42. },
  43. required: true,
  44. },
  45. ])
  46. const accessTokenCreateMutation = new MutationHandler(
  47. useUserCurrentAccessTokenAddMutation({
  48. update: (cache, { data }) => {
  49. if (!data) return
  50. const { userCurrentAccessTokenAdd } = data
  51. if (!userCurrentAccessTokenAdd?.token) return
  52. let existingAccessTokens =
  53. cache.readQuery<UserCurrentAccessTokenListQuery>({
  54. query: UserCurrentAccessTokenListDocument,
  55. })
  56. const newIdPresent =
  57. existingAccessTokens?.userCurrentAccessTokenList?.find((token) => {
  58. return token.id === userCurrentAccessTokenAdd.token?.id
  59. })
  60. if (newIdPresent) return
  61. existingAccessTokens = {
  62. ...existingAccessTokens,
  63. userCurrentAccessTokenList: [
  64. userCurrentAccessTokenAdd.token,
  65. ...(existingAccessTokens?.userCurrentAccessTokenList || []),
  66. ],
  67. }
  68. cache.writeQuery({
  69. query: UserCurrentAccessTokenListDocument,
  70. data: existingAccessTokens,
  71. })
  72. },
  73. }),
  74. {
  75. errorNotificationMessage: __('The access token could not be created.'),
  76. },
  77. )
  78. const accessToken = ref<string>('')
  79. const submitForm = (data: FormSubmitData<NewTokenAccessFormData>) => {
  80. return accessTokenCreateMutation
  81. .send({
  82. input: {
  83. name: data.name,
  84. expiresAt: data.expires_at,
  85. permission: data.permissions,
  86. },
  87. })
  88. .then((result) => {
  89. if (result?.userCurrentAccessTokenAdd?.tokenValue) {
  90. accessToken.value = result.userCurrentAccessTokenAdd.tokenValue
  91. }
  92. })
  93. }
  94. const footerActionOptions = computed<ActionFooterOptions>(() => {
  95. if (accessToken.value) {
  96. return {
  97. actionLabel: __('OK, I have copied my token'),
  98. actionButton: { variant: 'primary' },
  99. hideCancelButton: true,
  100. }
  101. }
  102. return {
  103. actionLabel: __('Create'),
  104. actionButton: { variant: 'submit', type: 'submit' },
  105. form: form.value,
  106. }
  107. })
  108. const actionCloseFlyout = () => {
  109. if (accessToken.value) closeFlyout('new-access-token')
  110. }
  111. </script>
  112. <template>
  113. <CommonFlyout
  114. :header-title="__('New Personal Access Token')"
  115. :footer-action-options="footerActionOptions"
  116. header-icon="key"
  117. no-close-on-action
  118. name="new-access-token"
  119. @action="actionCloseFlyout()"
  120. @activated="form?.triggerFormUpdater"
  121. >
  122. <div v-if="accessToken" class="flex flex-col gap-3">
  123. <CommonLabel>{{
  124. $t(
  125. "For security reasons, the API token is shown only once. You'll need to save it somewhere secure before continuing.",
  126. )
  127. }}</CommonLabel>
  128. <CommonInputCopyToClipboard
  129. :value="accessToken"
  130. :label="__('Your Personal Access Token')"
  131. :copy-button-text="__('Copy Token')"
  132. />
  133. </div>
  134. <Form
  135. v-else
  136. ref="form"
  137. :schema="formSchema"
  138. :form-updater-id="
  139. EnumFormUpdaterId.FormUpdaterUpdaterUserCurrentNewAccessToken
  140. "
  141. form-updater-initial-only
  142. should-autofocus
  143. @submit="submitForm($event as FormSubmitData<NewTokenAccessFormData>)"
  144. />
  145. </CommonFlyout>
  146. </template>