diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-14 21:08:31 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-14 21:08:31 +0300 |
commit | 06ac12d53c3f0b7cee2755a1254bf1af05d55044 (patch) | |
tree | 95d0be0bd751a22d6135f496c425c44d774fbe54 /app/assets/javascripts/admin | |
parent | b689f371350fbf1b71f266764ee018befc9b91f7 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/admin')
4 files changed, 283 insertions, 0 deletions
diff --git a/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue b/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue new file mode 100644 index 00000000000..413163c8536 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue @@ -0,0 +1,151 @@ +<script> +import { GlModal, GlButton, GlFormInput, GlSprintf } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; +import { s__, sprintf } from '~/locale'; +import OncallSchedulesList from '~/vue_shared/components/oncall_schedules_list.vue'; + +export default { + components: { + GlModal, + GlButton, + GlFormInput, + GlSprintf, + OncallSchedulesList, + }, + props: { + title: { + type: String, + required: true, + }, + content: { + type: String, + required: true, + }, + action: { + type: String, + required: true, + }, + secondaryAction: { + type: String, + required: true, + }, + deleteUserUrl: { + type: String, + required: true, + }, + blockUserUrl: { + type: String, + required: true, + }, + username: { + type: String, + required: true, + }, + csrfToken: { + type: String, + required: true, + }, + oncallSchedules: { + type: String, + required: false, + default: '[]', + }, + }, + data() { + return { + enteredUsername: '', + }; + }, + computed: { + modalTitle() { + return sprintf(this.title, { username: this.username }, false); + }, + secondaryButtonLabel() { + return s__('AdminUsers|Block user'); + }, + canSubmit() { + return this.enteredUsername === this.username; + }, + schedules() { + try { + return JSON.parse(this.oncallSchedules); + } catch (e) { + Sentry.captureException(e); + } + return []; + }, + }, + methods: { + show() { + this.$refs.modal.show(); + }, + onCancel() { + this.enteredUsername = ''; + this.$refs.modal.hide(); + }, + onSecondaryAction() { + const { form } = this.$refs; + + form.action = this.blockUserUrl; + this.$refs.method.value = 'put'; + + form.submit(); + }, + onSubmit() { + this.$refs.form.submit(); + this.enteredUsername = ''; + }, + }, +}; +</script> + +<template> + <gl-modal ref="modal" modal-id="delete-user-modal" :title="modalTitle" kind="danger"> + <p> + <gl-sprintf :message="content"> + <template #username> + <strong>{{ username }}</strong> + </template> + <template #strong="props"> + <strong>{{ props.content }}</strong> + </template> + </gl-sprintf> + </p> + + <oncall-schedules-list v-if="schedules.length" :schedules="schedules" :user-name="username" /> + + <p> + <gl-sprintf :message="s__('AdminUsers|To confirm, type %{username}')"> + <template #username> + <code>{{ username }}</code> + </template> + </gl-sprintf> + </p> + + <form ref="form" :action="deleteUserUrl" method="post" @submit.prevent> + <input ref="method" type="hidden" name="_method" value="delete" /> + <input :value="csrfToken" type="hidden" name="authenticity_token" /> + <gl-form-input + v-model="enteredUsername" + autofocus + type="text" + name="username" + autocomplete="off" + /> + </form> + <template #modal-footer> + <gl-button @click="onCancel">{{ s__('Cancel') }}</gl-button> + <gl-button + :disabled="!canSubmit" + category="secondary" + variant="danger" + @click="onSecondaryAction" + > + {{ secondaryAction }} + </gl-button> + <gl-button :disabled="!canSubmit" category="primary" variant="danger" @click="onSubmit">{{ + action + }}</gl-button> + </template> + </gl-modal> +</template> diff --git a/app/assets/javascripts/admin/users/components/modals/user_modal_manager.vue b/app/assets/javascripts/admin/users/components/modals/user_modal_manager.vue new file mode 100644 index 00000000000..1dfea3f1e7b --- /dev/null +++ b/app/assets/javascripts/admin/users/components/modals/user_modal_manager.vue @@ -0,0 +1,77 @@ +<script> +import DeleteUserModal from './delete_user_modal.vue'; + +export default { + components: { DeleteUserModal }, + props: { + modalConfiguration: { + required: true, + type: Object, + }, + csrfToken: { + required: true, + type: String, + }, + selector: { + required: true, + type: String, + }, + }, + data() { + return { + currentModalData: null, + }; + }, + computed: { + activeModal() { + return Boolean(this.currentModalData); + }, + + modalProps() { + const { glModalAction: requestedAction } = this.currentModalData; + return { + ...this.modalConfiguration[requestedAction], + ...this.currentModalData, + csrfToken: this.csrfToken, + }; + }, + }, + + mounted() { + /* + * Here we're looking for every button that needs to launch a modal + * on click, and then attaching a click event handler to show the modal + * if it's correctly configured. + * + * TODO: Replace this with integrated modal components https://gitlab.com/gitlab-org/gitlab/-/issues/320922 + */ + document.querySelectorAll(this.selector).forEach((button) => { + button.addEventListener('click', (e) => { + if (!button.dataset.glModalAction) return; + + e.preventDefault(); + this.show(button.dataset); + }); + }); + }, + + methods: { + show(modalData) { + const { glModalAction: requestedAction } = modalData; + + if (!this.modalConfiguration[requestedAction]) { + throw new Error(`Modal action ${requestedAction} has no configuration in HTML`); + } + + this.currentModalData = modalData; + + return this.$nextTick().then(() => { + this.$refs.modal.show(); + }); + }, + }, +}; +</script> +<template> + <delete-user-modal v-if="activeModal" ref="modal" v-bind="modalProps" /> +</template> diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js index d0421413132..33ee7e1cb0d 100644 --- a/app/assets/javascripts/admin/users/constants.js +++ b/app/assets/javascripts/admin/users/constants.js @@ -20,3 +20,9 @@ export const I18N_USER_ACTIONS = { ban: s__('AdminUsers|Ban user'), unban: s__('AdminUsers|Unban user'), }; + +export const CONFIRM_DELETE_BUTTON_SELECTOR = '.js-delete-user-modal-button'; + +export const MODAL_TEXTS_CONTAINER_SELECTOR = '#js-modal-texts'; + +export const MODAL_MANAGER_SELECTOR = '#js-delete-user-modal'; diff --git a/app/assets/javascripts/admin/users/index.js b/app/assets/javascripts/admin/users/index.js index 54c8edc080b..05f8469e61a 100644 --- a/app/assets/javascripts/admin/users/index.js +++ b/app/assets/javascripts/admin/users/index.js @@ -2,7 +2,14 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import csrf from '~/lib/utils/csrf'; import AdminUsersApp from './components/app.vue'; +import ModalManager from './components/modals/user_modal_manager.vue'; +import { + CONFIRM_DELETE_BUTTON_SELECTOR, + MODAL_TEXTS_CONTAINER_SELECTOR, + MODAL_MANAGER_SELECTOR, +} from './constants'; Vue.use(VueApollo); @@ -29,3 +36,45 @@ export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-a }), }); }; + +export const initDeleteUserModals = () => { + const modalsMountElement = document.querySelector(MODAL_TEXTS_CONTAINER_SELECTOR); + + if (!modalsMountElement) { + return; + } + + const modalConfiguration = Array.from(modalsMountElement.children).reduce((accumulator, node) => { + const { modal, ...config } = node.dataset; + + return { + ...accumulator, + [modal]: { + title: node.dataset.title, + ...config, + content: node.innerHTML, + }, + }; + }, {}); + + // eslint-disable-next-line no-new + new Vue({ + el: MODAL_MANAGER_SELECTOR, + functional: true, + methods: { + show(...args) { + this.$refs.manager.show(...args); + }, + }, + render(h) { + return h(ModalManager, { + ref: 'manager', + props: { + selector: CONFIRM_DELETE_BUTTON_SELECTOR, + modalConfiguration, + csrfToken: csrf.token, + }, + }); + }, + }); +}; |