diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-22 11:31:16 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-22 11:31:16 +0000 |
commit | 905c1110b08f93a19661cf42a276c7ea90d0a0ff (patch) | |
tree | 756d138db422392c00471ab06acdff92c5a9b69c /app/assets/javascripts/pages/admin | |
parent | 50d93f8d1686950fc58dda4823c4835fd0d8c14b (diff) | |
download | gitlab-ce-905c1110b08f93a19661cf42a276c7ea90d0a0ff.tar.gz |
Add latest changes from gitlab-org/gitlab@12-4-stable-ee
Diffstat (limited to 'app/assets/javascripts/pages/admin')
5 files changed, 239 insertions, 100 deletions
diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue index e2fec3c7172..eb03baf4894 100644 --- a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue +++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue @@ -1,13 +1,13 @@ <script> import axios from '~/lib/utils/axios_utils'; import createFlash from '~/flash'; -import GlModal from '~/vue_shared/components/gl_modal.vue'; +import DeprecatedModal2 from '~/vue_shared/components/deprecated_modal_2.vue'; import { redirectTo } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; export default { components: { - GlModal, + GlModal: DeprecatedModal2, }, props: { url: { diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue index e8905b479ee..78aaa9df0ec 100644 --- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -1,37 +1,46 @@ <script> import _ from 'underscore'; -import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; +import { GlModal, GlButton, GlFormInput } from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; export default { components: { - DeprecatedModal, + GlModal, + GlButton, + GlFormInput, }, 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: false, - default: '', + required: true, }, blockUserUrl: { type: String, - required: false, - default: '', - }, - deleteContributions: { - type: Boolean, - required: false, - default: false, + required: true, }, username: { type: String, - required: false, - default: '', + required: true, }, csrfToken: { type: String, - required: false, - default: '', + required: true, }, }, data() { @@ -40,32 +49,12 @@ export default { }; }, computed: { - title() { - const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?'); - const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?'); - - return sprintf( - this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle, - { - username: `'${_.escape(this.username)}'`, - }, - false, - ); + modalTitle() { + return sprintf(this.title, { username: this.username }); }, text() { - const keepContributionsText = s__(`AdminArea| - You are about to permanently delete the user %{username}. - Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". - To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. - Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); - - const deleteContributionsText = s__(`AdminArea| - You are about to permanently delete the user %{username}. - This will delete all of the issues, merge requests, and groups linked to them. - To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. - Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); return sprintf( - this.deleteContributions ? deleteContributionsText : keepContributionsText, + this.content, { username: `<strong>${_.escape(this.username)}</strong>`, strong_start: '<strong>', @@ -83,12 +72,7 @@ export default { false, ); }, - primaryButtonLabel() { - const keepContributionsLabel = s__('AdminUsers|Delete user'); - const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions'); - return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel; - }, secondaryButtonLabel() { return s__('AdminUsers|Block user'); }, @@ -97,8 +81,12 @@ export default { }, }, methods: { + show() { + this.$refs.modal.show(); + }, onCancel() { this.enteredUsername = ''; + this.$refs.modal.hide(); }, onSecondaryAction() { const { form } = this.$refs; @@ -117,43 +105,28 @@ export default { </script> <template> - <deprecated-modal - id="delete-user-modal" - :title="title" - :text="text" - :primary-button-label="primaryButtonLabel" - :secondary-button-label="secondaryButtonLabel" - :submit-disabled="!canSubmit" - kind="danger" - @submit="onSubmit" - @cancel="onCancel" - > - <template slot="body" slot-scope="props"> - <p v-html="props.text"></p> + <gl-modal ref="modal" modal-id="delete-user-modal" :title="modalTitle" kind="danger"> + <template> + <p v-html="text"></p> <p v-html="confirmationTextLabel"></p> <form ref="form" :action="deleteUserUrl" method="post"> <input ref="method" type="hidden" name="_method" value="delete" /> <input :value="csrfToken" type="hidden" name="authenticity_token" /> - <input + <gl-form-input v-model="enteredUsername" + autofocus type="text" name="username" - class="form-control" - aria-labelledby="input-label" autocomplete="off" /> </form> </template> - <template slot="secondary-button"> - <button - :disabled="!canSubmit" - type="button" - class="btn js-secondary-button btn-warning" - data-dismiss="modal" - @click="onSecondaryAction" - > - {{ secondaryButtonLabel }} - </button> + <template slot="modal-footer"> + <gl-button variant="secondary" @click="onCancel">{{ s__('Cancel') }}</gl-button> + <gl-button :disabled="!canSubmit" variant="warning" @click="onSecondaryAction"> + {{ secondaryAction }} + </gl-button> + <gl-button :disabled="!canSubmit" variant="danger" @click="onSubmit">{{ action }}</gl-button> </template> - </deprecated-modal> + </gl-modal> </template> diff --git a/app/assets/javascripts/pages/admin/users/components/user_modal_manager.vue b/app/assets/javascripts/pages/admin/users/components/user_modal_manager.vue new file mode 100644 index 00000000000..a08d32028c3 --- /dev/null +++ b/app/assets/javascripts/pages/admin/users/components/user_modal_manager.vue @@ -0,0 +1,77 @@ +<script> +export default { + props: { + modalConfiguration: { + required: true, + type: Object, + }, + actionModals: { + required: true, + type: Object, + }, + csrfToken: { + required: true, + type: String, + }, + }, + data() { + return { + currentModalData: null, + }; + }, + computed: { + activeModal() { + if (!this.currentModalData) return null; + const { glModalAction: action } = this.currentModalData; + + return this.actionModals[action]; + }, + + modalProps() { + const { glModalAction: requestedAction } = this.currentModalData; + return { + ...this.modalConfiguration[requestedAction], + ...this.currentModalData, + csrfToken: this.csrfToken, + }; + }, + }, + + mounted() { + document.addEventListener('click', this.handleClick); + }, + + beforeDestroy() { + document.removeEventListener('click', this.handleClick); + }, + + methods: { + handleClick(e) { + const { glModalAction: action } = e.target.dataset; + if (!action) return; + + this.show(e.target.dataset); + e.preventDefault(); + }, + + show(modalData) { + const { glModalAction: requestedAction } = modalData; + if (!this.actionModals[requestedAction]) { + throw new Error(`Requested non-existing modal action ${requestedAction}`); + } + 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> + <div :is="activeModal" v-if="activeModal" ref="modal" v-bind="modalProps" /> +</template> diff --git a/app/assets/javascripts/pages/admin/users/components/user_operation_confirmation_modal.vue b/app/assets/javascripts/pages/admin/users/components/user_operation_confirmation_modal.vue new file mode 100644 index 00000000000..4c335cfb018 --- /dev/null +++ b/app/assets/javascripts/pages/admin/users/components/user_operation_confirmation_modal.vue @@ -0,0 +1,70 @@ +<script> +import { GlModal } from '@gitlab/ui'; +import { sprintf } from '~/locale'; + +export default { + components: { + GlModal, + }, + props: { + title: { + type: String, + required: true, + }, + content: { + type: String, + required: true, + }, + action: { + type: String, + required: true, + }, + url: { + type: String, + required: true, + }, + username: { + type: String, + required: true, + }, + csrfToken: { + type: String, + required: true, + }, + method: { + type: String, + required: false, + default: 'put', + }, + }, + computed: { + modalTitle() { + return sprintf(this.title, { username: this.username }); + }, + }, + methods: { + show() { + this.$refs.modal.show(); + }, + submit() { + this.$refs.form.submit(); + }, + }, +}; +</script> +<template> + <gl-modal + ref="modal" + modal-id="user-operation-modal" + :title="modalTitle" + ok-variant="warning" + :ok-title="action" + @ok="submit" + > + <form ref="form" :action="url" method="post"> + <span v-html="content"></span> + <input ref="method" type="hidden" name="_method" :value="method" /> + <input :value="csrfToken" type="hidden" name="authenticity_token" /> + </form> + </gl-modal> +</template> diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js index 45046688b57..bc96e88351b 100644 --- a/app/assets/javascripts/pages/admin/users/index.js +++ b/app/assets/javascripts/pages/admin/users/index.js @@ -1,46 +1,65 @@ -import $ from 'jquery'; import Vue from 'vue'; import Translate from '~/vue_shared/translate'; +import ModalManager from './components/user_modal_manager.vue'; +import DeleteUserModal from './components/delete_user_modal.vue'; +import UserOperationConfirmationModal from './components/user_operation_confirmation_modal.vue'; import csrf from '~/lib/utils/csrf'; -import deleteUserModal from './components/delete_user_modal.vue'; +const MODAL_TEXTS_CONTAINER_SELECTOR = '#modal-texts'; +const MODAL_MANAGER_SELECTOR = '#user-modal'; +const ACTION_MODALS = { + deactivate: UserOperationConfirmationModal, + block: UserOperationConfirmationModal, + delete: DeleteUserModal, + 'delete-with-contributions': DeleteUserModal, +}; + +function loadModalsConfigurationFromHtml(modalsElement) { + const modalsConfiguration = {}; + + if (!modalsElement) { + /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ + throw new Error('Modals content element not found!'); + } + + Array.from(modalsElement.children).forEach(node => { + const { modal, ...config } = node.dataset; + modalsConfiguration[modal] = { + title: node.dataset.title, + ...config, + content: node.innerHTML, + }; + }); + + return modalsConfiguration; +} document.addEventListener('DOMContentLoaded', () => { Vue.use(Translate); - const deleteUserModalEl = document.getElementById('delete-user-modal'); + const modalConfiguration = loadModalsConfigurationFromHtml( + document.querySelector(MODAL_TEXTS_CONTAINER_SELECTOR), + ); - const deleteModal = new Vue({ - el: deleteUserModalEl, - data: { - deleteUserUrl: '', - blockUserUrl: '', - deleteContributions: '', - username: '', + // eslint-disable-next-line no-new + new Vue({ + el: MODAL_MANAGER_SELECTOR, + functional: true, + methods: { + show(...args) { + this.$refs.manager.show(...args); + }, }, - render(createElement) { - return createElement(deleteUserModal, { + render(h) { + return h(ModalManager, { + ref: 'manager', props: { - deleteUserUrl: this.deleteUserUrl, - blockUserUrl: this.blockUserUrl, - deleteContributions: this.deleteContributions, - username: this.username, + modalConfiguration, + actionModals: ACTION_MODALS, csrfToken: csrf.token, }, }); }, }); - - $(document).on('shown.bs.modal', event => { - if (event.relatedTarget.classList.contains('delete-user-button')) { - const buttonProps = event.relatedTarget.dataset; - deleteModal.deleteUserUrl = buttonProps.deleteUserUrl; - deleteModal.blockUserUrl = buttonProps.blockUserUrl; - deleteModal.deleteContributions = event.relatedTarget.hasAttribute( - 'data-delete-contributions', - ); - deleteModal.username = buttonProps.username; - } - }); }); |