diff options
Diffstat (limited to 'app/assets/javascripts/ci/runner/components/registration')
3 files changed, 282 insertions, 0 deletions
diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue new file mode 100644 index 00000000000..212ad5fa5a0 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/registration/registration_dropdown.vue @@ -0,0 +1,92 @@ +<script> +import { GlDropdown, GlDropdownForm, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; +import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants'; +import RegistrationToken from './registration_token.vue'; +import RegistrationTokenResetDropdownItem from './registration_token_reset_dropdown_item.vue'; + +export default { + i18n: { + showInstallationInstructions: s__( + 'Runners|Show runner installation and registration instructions', + ), + }, + components: { + GlDropdown, + GlDropdownForm, + GlDropdownItem, + GlDropdownDivider, + RegistrationToken, + RunnerInstructionsModal, + RegistrationTokenResetDropdownItem, + }, + props: { + registrationToken: { + type: String, + required: true, + }, + type: { + type: String, + required: true, + validator(type) { + return [INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE].includes(type); + }, + }, + }, + data() { + return { + currentRegistrationToken: this.registrationToken, + }; + }, + computed: { + dropdownText() { + switch (this.type) { + case INSTANCE_TYPE: + return s__('Runners|Register an instance runner'); + case GROUP_TYPE: + return s__('Runners|Register a group runner'); + case PROJECT_TYPE: + return s__('Runners|Register a project runner'); + default: + return s__('Runners|Register a runner'); + } + }, + }, + methods: { + onShowInstructionsClick() { + this.$refs.runnerInstructionsModal.show(); + }, + onTokenReset(token) { + this.currentRegistrationToken = token; + + this.$refs.runnerRegistrationDropdown.hide(true); + }, + }, +}; +</script> + +<template> + <gl-dropdown + ref="runnerRegistrationDropdown" + menu-class="gl-w-auto!" + :text="dropdownText" + variant="confirm" + v-bind="$attrs" + > + <gl-dropdown-item @click.capture.native.stop="onShowInstructionsClick"> + {{ $options.i18n.showInstallationInstructions }} + <runner-instructions-modal + ref="runnerInstructionsModal" + :registration-token="currentRegistrationToken" + data-testid="runner-instructions-modal" + /> + </gl-dropdown-item> + <gl-dropdown-divider /> + <gl-dropdown-form class="gl-p-4!"> + <registration-token input-id="token-value" :value="currentRegistrationToken" /> + </gl-dropdown-form> + <gl-dropdown-divider /> + <registration-token-reset-dropdown-item :type="type" @tokenReset="onTokenReset" /> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_token.vue b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue new file mode 100644 index 00000000000..6b4e6a929b7 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/registration/registration_token.vue @@ -0,0 +1,49 @@ +<script> +import { s__ } from '~/locale'; +import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue'; + +export default { + components: { + InputCopyToggleVisibility, + }, + i18n: { + registrationToken: s__('Runners|Registration token'), + }, + props: { + inputId: { + type: String, + required: true, + }, + value: { + type: String, + required: false, + default: '', + }, + }, + computed: { + formInputGroupProps() { + return { + id: this.inputId, + }; + }, + }, + methods: { + onCopy() { + // value already in the clipboard, simply notify the user + this.$toast?.show(s__('Runners|Registration token copied!')); + }, + }, + I18N_COPY_BUTTON_TITLE: s__('Runners|Copy registration token'), +}; +</script> +<template> + <input-copy-toggle-visibility + class="gl-m-0" + :value="value" + :label="$options.i18n.registrationToken" + :label-for="inputId" + :copy-button-title="$options.I18N_COPY_BUTTON_TITLE" + :form-input-group-props="formInputGroupProps" + @copy="onCopy" + /> +</template> diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue new file mode 100644 index 00000000000..6740065e860 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/registration/registration_token_reset_dropdown_item.vue @@ -0,0 +1,141 @@ +<script> +import { GlDropdownItem, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui'; +import { createAlert } from '~/flash'; +import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { __, s__ } from '~/locale'; +import runnersRegistrationTokenResetMutation from '~/ci/runner/graphql/list/runners_registration_token_reset.mutation.graphql'; +import { captureException } from '~/ci/runner/sentry_utils'; +import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants'; + +const i18n = { + modalAction: s__('Runners|Reset token'), + modalCancel: __('Cancel'), + modalCopy: __('Are you sure you want to reset the registration token?'), + modalTitle: __('Reset registration token'), +}; + +export default { + name: 'RunnerRegistrationTokenReset', + i18n, + components: { + GlDropdownItem, + GlLoadingIcon, + GlModal, + }, + directives: { + GlModal: GlModalDirective, + }, + inject: { + groupId: { + default: null, + }, + projectId: { + default: null, + }, + }, + modalId: 'token-reset-modal', + props: { + type: { + type: String, + required: true, + validator(type) { + return [INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE].includes(type); + }, + }, + }, + data() { + return { + loading: false, + }; + }, + computed: { + resetTokenInput() { + switch (this.type) { + case INSTANCE_TYPE: + return { + type: this.type, + }; + case GROUP_TYPE: + return { + id: convertToGraphQLId(TYPE_GROUP, this.groupId), + type: this.type, + }; + case PROJECT_TYPE: + return { + id: convertToGraphQLId(TYPE_PROJECT, this.projectId), + type: this.type, + }; + default: + return null; + } + }, + actionPrimary() { + return { + text: i18n.modalAction, + attributes: [{ variant: 'danger' }], + }; + }, + actionSecondary() { + return { + text: i18n.modalCancel, + attributes: [{ variant: 'default' }], + }; + }, + }, + methods: { + handleModalPrimary() { + this.resetToken(); + }, + async resetToken() { + this.loading = true; + try { + const { + data: { + runnersRegistrationTokenReset: { token, errors }, + }, + } = await this.$apollo.mutate({ + mutation: runnersRegistrationTokenResetMutation, + variables: { + input: this.resetTokenInput, + }, + }); + if (errors && errors.length) { + throw new Error(errors.join(' ')); + } + this.onSuccess(token); + } catch (e) { + this.onError(e); + } finally { + this.loading = false; + } + }, + onError(error) { + const { message } = error; + + createAlert({ message }); + captureException({ error, component: this.$options.name }); + }, + onSuccess(token) { + this.$toast?.show(s__('Runners|New registration token generated!')); + this.$emit('tokenReset', token); + }, + }, +}; +</script> +<template> + <gl-dropdown-item v-gl-modal="$options.modalId"> + {{ __('Reset registration token') }} + <gl-modal + size="sm" + :modal-id="$options.modalId" + :action-primary="actionPrimary" + :action-secondary="actionSecondary" + :title="$options.i18n.modalTitle" + @primary="handleModalPrimary" + > + <p>{{ $options.i18n.modalCopy }}</p> + </gl-modal> + <gl-loading-icon v-if="loading" inline /> + </gl-dropdown-item> +</template> |