summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/admin/users
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/admin/users')
-rw-r--r--app/assets/javascripts/admin/users/components/actions/activate.vue31
-rw-r--r--app/assets/javascripts/admin/users/components/actions/approve.vue41
-rw-r--r--app/assets/javascripts/admin/users/components/actions/ban.vue69
-rw-r--r--app/assets/javascripts/admin/users/components/actions/block.vue21
-rw-r--r--app/assets/javascripts/admin/users/components/actions/deactivate.vue23
-rw-r--r--app/assets/javascripts/admin/users/components/actions/index.js4
-rw-r--r--app/assets/javascripts/admin/users/components/actions/reject.vue51
-rw-r--r--app/assets/javascripts/admin/users/components/actions/unban.vue53
-rw-r--r--app/assets/javascripts/admin/users/components/actions/unblock.vue20
-rw-r--r--app/assets/javascripts/admin/users/components/actions/unlock.vue18
-rw-r--r--app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue151
-rw-r--r--app/assets/javascripts/admin/users/components/modals/user_modal_manager.vue77
-rw-r--r--app/assets/javascripts/admin/users/components/user_actions.vue117
-rw-r--r--app/assets/javascripts/admin/users/constants.js10
-rw-r--r--app/assets/javascripts/admin/users/index.js65
15 files changed, 662 insertions, 89 deletions
diff --git a/app/assets/javascripts/admin/users/components/actions/activate.vue b/app/assets/javascripts/admin/users/components/actions/activate.vue
index 99c260bf11e..74e9c60a57b 100644
--- a/app/assets/javascripts/admin/users/components/actions/activate.vue
+++ b/app/assets/javascripts/admin/users/components/actions/activate.vue
@@ -1,6 +1,16 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
-import { sprintf, s__ } from '~/locale';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
+
+// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
+const messageHtml = `
+ <p>${s__('AdminUsers|Reactivating a user will:')}</p>
+ <ul>
+ <li>${s__('AdminUsers|Restore user access to the account, including web, Git and API.')}</li>
+ </ul>
+ <p>${s__('AdminUsers|You can always deactivate their account again if needed.')}</p>
+`;
export default {
components: {
@@ -25,9 +35,14 @@ export default {
title: sprintf(s__('AdminUsers|Activate user %{username}?'), {
username: this.username,
}),
- message: s__('AdminUsers|You can always deactivate their account again if needed.'),
- okVariant: 'confirm',
- okTitle: s__('AdminUsers|Activate'),
+ messageHtml,
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.activate,
+ attributes: [{ variant: 'confirm' }],
+ },
}),
};
},
@@ -36,9 +51,7 @@ export default {
</script>
<template>
- <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
- <gl-dropdown-item>
- <slot></slot>
- </gl-dropdown-item>
- </div>
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
+ <slot></slot>
+ </gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/approve.vue b/app/assets/javascripts/admin/users/components/actions/approve.vue
index 6fc43c246ea..77a9be8eec2 100644
--- a/app/assets/javascripts/admin/users/components/actions/approve.vue
+++ b/app/assets/javascripts/admin/users/components/actions/approve.vue
@@ -1,21 +1,60 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
+
+// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
+const messageHtml = `
+ <p>${s__('AdminUsers|Approved users can:')}</p>
+ <ul>
+ <li>${s__('AdminUsers|Log in')}</li>
+ <li>${s__('AdminUsers|Access Git repositories')}</li>
+ <li>${s__('AdminUsers|Access the API')}</li>
+ <li>${s__('AdminUsers|Be added to groups and projects')}</li>
+ </ul>
+`;
export default {
components: {
GlDropdownItem,
},
props: {
+ username: {
+ type: String,
+ required: true,
+ },
path: {
type: String,
required: true,
},
},
+ computed: {
+ attributes() {
+ return {
+ 'data-path': this.path,
+ 'data-method': 'put',
+ 'data-modal-attributes': JSON.stringify({
+ title: sprintf(s__('AdminUsers|Approve user %{username}?'), {
+ username: this.username,
+ }),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.approve,
+ attributes: [{ variant: 'confirm', 'data-qa-selector': 'approve_user_confirm_button' }],
+ },
+ messageHtml,
+ }),
+ 'data-qa-selector': 'approve_user_button',
+ };
+ },
+ },
};
</script>
<template>
- <gl-dropdown-item :href="path" data-method="put">
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...attributes }">
<slot></slot>
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/ban.vue b/app/assets/javascripts/admin/users/components/actions/ban.vue
new file mode 100644
index 00000000000..4e9cefbfdd7
--- /dev/null
+++ b/app/assets/javascripts/admin/users/components/actions/ban.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlDropdownItem } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
+
+// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
+const messageHtml = `
+ <p>${s__('AdminUsers|When banned, users:')}</p>
+ <ul>
+ <li>${s__("AdminUsers|Can't log in.")}</li>
+ <li>${s__("AdminUsers|Can't access Git repositories.")}</li>
+ </ul>
+ <p>${s__('AdminUsers|You can unban their account in the future. Their data remains intact.')}</p>
+ <p>${sprintf(
+ s__('AdminUsers|Learn more about %{link_start}banned users.%{link_end}'),
+ {
+ link_start: `<a href="${helpPagePath('user/admin_area/moderate_users', {
+ anchor: 'ban-a-user',
+ })}" target="_blank">`,
+ link_end: '</a>',
+ },
+ false,
+ )}</p>
+`;
+
+export default {
+ components: {
+ GlDropdownItem,
+ },
+ props: {
+ username: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ modalAttributes() {
+ return {
+ 'data-path': this.path,
+ 'data-method': 'put',
+ 'data-modal-attributes': JSON.stringify({
+ title: sprintf(s__('AdminUsers|Ban user %{username}?'), {
+ username: this.username,
+ }),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.ban,
+ attributes: [{ variant: 'confirm' }],
+ },
+ messageHtml,
+ }),
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
+ <slot></slot>
+ </gl-dropdown-item>
+</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/block.vue b/app/assets/javascripts/admin/users/components/actions/block.vue
index 68dfefe14c2..03557008a89 100644
--- a/app/assets/javascripts/admin/users/components/actions/block.vue
+++ b/app/assets/javascripts/admin/users/components/actions/block.vue
@@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
-import { sprintf, s__ } from '~/locale';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
@@ -11,6 +12,7 @@ const messageHtml = `
<li>${s__('AdminUsers|Personal projects will be left')}</li>
<li>${s__('AdminUsers|Owned groups will be left')}</li>
</ul>
+ <p>${s__('AdminUsers|You can always unblock their account, their data will remain intact.')}</p>
`;
export default {
@@ -34,8 +36,13 @@ export default {
'data-method': 'put',
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Block user %{username}?'), { username: this.username }),
- okVariant: 'confirm',
- okTitle: s__('AdminUsers|Block'),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.block,
+ attributes: [{ variant: 'confirm' }],
+ },
messageHtml,
}),
};
@@ -45,9 +52,7 @@ export default {
</script>
<template>
- <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
- <gl-dropdown-item>
- <slot></slot>
- </gl-dropdown-item>
- </div>
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
+ <slot></slot>
+ </gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/deactivate.vue b/app/assets/javascripts/admin/users/components/actions/deactivate.vue
index 7e0c17ba296..640c8fefc20 100644
--- a/app/assets/javascripts/admin/users/components/actions/deactivate.vue
+++ b/app/assets/javascripts/admin/users/components/actions/deactivate.vue
@@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
-import { sprintf, s__ } from '~/locale';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
@@ -16,6 +17,9 @@ const messageHtml = `
)}</li>
<li>${s__('AdminUsers|Personal projects, group and user history will be left intact')}</li>
</ul>
+ <p>${s__(
+ 'AdminUsers|You can always re-activate their account, their data will remain intact.',
+ )}</p>
`;
export default {
@@ -41,8 +45,13 @@ export default {
title: sprintf(s__('AdminUsers|Deactivate user %{username}?'), {
username: this.username,
}),
- okVariant: 'confirm',
- okTitle: s__('AdminUsers|Deactivate'),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.deactivate,
+ attributes: [{ variant: 'confirm' }],
+ },
messageHtml,
}),
};
@@ -52,9 +61,7 @@ export default {
</script>
<template>
- <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
- <gl-dropdown-item>
- <slot></slot>
- </gl-dropdown-item>
- </div>
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
+ <slot></slot>
+ </gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/index.js b/app/assets/javascripts/admin/users/components/actions/index.js
index e34b01346b9..4e63a85df89 100644
--- a/app/assets/javascripts/admin/users/components/actions/index.js
+++ b/app/assets/javascripts/admin/users/components/actions/index.js
@@ -1,20 +1,24 @@
import Activate from './activate.vue';
import Approve from './approve.vue';
+import Ban from './ban.vue';
import Block from './block.vue';
import Deactivate from './deactivate.vue';
import Delete from './delete.vue';
import DeleteWithContributions from './delete_with_contributions.vue';
import Reject from './reject.vue';
+import Unban from './unban.vue';
import Unblock from './unblock.vue';
import Unlock from './unlock.vue';
export default {
Activate,
Approve,
+ Ban,
Block,
Deactivate,
Delete,
DeleteWithContributions,
+ Unban,
Unblock,
Unlock,
Reject,
diff --git a/app/assets/javascripts/admin/users/components/actions/reject.vue b/app/assets/javascripts/admin/users/components/actions/reject.vue
index a80c1ff5458..901306455fa 100644
--- a/app/assets/javascripts/admin/users/components/actions/reject.vue
+++ b/app/assets/javascripts/admin/users/components/actions/reject.vue
@@ -1,21 +1,70 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
+
+// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
+const messageHtml = `
+ <p>${s__('AdminUsers|Rejected users:')}</p>
+ <ul>
+ <li>${s__('AdminUsers|Cannot sign in or access instance information')}</li>
+ <li>${s__('AdminUsers|Will be deleted')}</li>
+ </ul>
+ <p>${sprintf(
+ s__(
+ 'AdminUsers|For more information, please refer to the %{link_start}user account deletion documentation.%{link_end}',
+ ),
+ {
+ link_start: `<a href="${helpPagePath('user/profile/account/delete_account', {
+ anchor: 'associated-records',
+ })}" target="_blank">`,
+ link_end: '</a>',
+ },
+ false,
+ )}</p>
+`;
export default {
components: {
GlDropdownItem,
},
props: {
+ username: {
+ type: String,
+ required: true,
+ },
path: {
type: String,
required: true,
},
},
+ computed: {
+ modalAttributes() {
+ return {
+ 'data-path': this.path,
+ 'data-method': 'delete',
+ 'data-modal-attributes': JSON.stringify({
+ title: sprintf(s__('AdminUsers|Reject user %{username}?'), {
+ username: this.username,
+ }),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.reject,
+ attributes: [{ variant: 'danger' }],
+ },
+ messageHtml,
+ }),
+ };
+ },
+ },
};
</script>
<template>
- <gl-dropdown-item :href="path" data-method="delete">
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/unban.vue b/app/assets/javascripts/admin/users/components/actions/unban.vue
new file mode 100644
index 00000000000..8083e26177e
--- /dev/null
+++ b/app/assets/javascripts/admin/users/components/actions/unban.vue
@@ -0,0 +1,53 @@
+<script>
+import { GlDropdownItem } from '@gitlab/ui';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
+
+// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
+const messageHtml = `<p>${s__(
+ 'AdminUsers|You can ban their account in the future if necessary.',
+)}</p>`;
+
+export default {
+ components: {
+ GlDropdownItem,
+ },
+ props: {
+ username: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ modalAttributes() {
+ return {
+ 'data-path': this.path,
+ 'data-method': 'put',
+ 'data-modal-attributes': JSON.stringify({
+ title: sprintf(s__('AdminUsers|Unban user %{username}?'), {
+ username: this.username,
+ }),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.unban,
+ attributes: [{ variant: 'confirm' }],
+ },
+ messageHtml,
+ }),
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
+ <slot></slot>
+ </gl-dropdown-item>
+</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/unblock.vue b/app/assets/javascripts/admin/users/components/actions/unblock.vue
index d4c0f900c94..7de6653e0cd 100644
--- a/app/assets/javascripts/admin/users/components/actions/unblock.vue
+++ b/app/assets/javascripts/admin/users/components/actions/unblock.vue
@@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
-import { sprintf, s__ } from '~/locale';
+import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
export default {
components: {
@@ -24,8 +25,13 @@ export default {
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Unblock user %{username}?'), { username: this.username }),
message: s__('AdminUsers|You can always block their account again if needed.'),
- okVariant: 'confirm',
- okTitle: s__('AdminUsers|Unblock'),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.unblock,
+ attributes: [{ variant: 'confirm' }],
+ },
}),
};
},
@@ -34,9 +40,7 @@ export default {
</script>
<template>
- <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
- <gl-dropdown-item>
- <slot></slot>
- </gl-dropdown-item>
- </div>
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
+ <slot></slot>
+ </gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/admin/users/components/actions/unlock.vue b/app/assets/javascripts/admin/users/components/actions/unlock.vue
index 294aaade7c1..10d4fb06d61 100644
--- a/app/assets/javascripts/admin/users/components/actions/unlock.vue
+++ b/app/assets/javascripts/admin/users/components/actions/unlock.vue
@@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
+import { I18N_USER_ACTIONS } from '../../constants';
export default {
components: {
@@ -24,8 +25,13 @@ export default {
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Unlock user %{username}?'), { username: this.username }),
message: __('Are you sure?'),
- okVariant: 'confirm',
- okTitle: s__('AdminUsers|Unlock'),
+ actionCancel: {
+ text: __('Cancel'),
+ },
+ actionPrimary: {
+ text: I18N_USER_ACTIONS.unlock,
+ attributes: [{ variant: 'confirm' }],
+ },
}),
};
},
@@ -34,9 +40,7 @@ export default {
</script>
<template>
- <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
- <gl-dropdown-item>
- <slot></slot>
- </gl-dropdown-item>
- </div>
+ <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
+ <slot></slot>
+ </gl-dropdown-item>
</template>
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/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue
index b782526e6be..c076e0bedf0 100644
--- a/app/assets/javascripts/admin/users/components/user_actions.vue
+++ b/app/assets/javascripts/admin/users/components/user_actions.vue
@@ -5,6 +5,7 @@ import {
GlDropdownItem,
GlDropdownSectionHeader,
GlDropdownDivider,
+ GlTooltipDirective,
} from '@gitlab/ui';
import { convertArrayToCamelCase } from '~/lib/utils/common_utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
@@ -21,6 +22,9 @@ export default {
GlDropdownDivider,
...Actions,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
user: {
type: Object,
@@ -30,6 +34,11 @@ export default {
type: Object,
required: true,
},
+ showButtonLabels: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
userActions() {
@@ -56,6 +65,13 @@ export default {
userPaths() {
return generateUserPaths(this.paths, this.user.username);
},
+ editButtonAttrs() {
+ return {
+ 'data-testid': 'edit',
+ icon: 'pencil-square',
+ href: this.userPaths.edit,
+ };
+ },
},
methods: {
isLdapAction(action) {
@@ -70,51 +86,68 @@ export default {
</script>
<template>
- <div class="gl-display-flex gl-justify-content-end" :data-testid="`user-actions-${user.id}`">
- <gl-button v-if="hasEditAction" data-testid="edit" :href="userPaths.edit">{{
- $options.i18n.edit
- }}</gl-button>
+ <div
+ class="gl-display-flex gl-justify-content-end gl-my-n2 gl-mx-n2"
+ :data-testid="`user-actions-${user.id}`"
+ >
+ <div v-if="hasEditAction" class="gl-p-2">
+ <gl-button v-if="showButtonLabels" v-bind="editButtonAttrs">{{
+ $options.i18n.edit
+ }}</gl-button>
+ <gl-button
+ v-else
+ v-gl-tooltip="$options.i18n.edit"
+ v-bind="editButtonAttrs"
+ :aria-label="$options.i18n.edit"
+ />
+ </div>
- <gl-dropdown
- v-if="hasDropdownActions"
- data-testid="dropdown-toggle"
- right
- class="gl-ml-2"
- icon="settings"
- >
- <gl-dropdown-section-header>{{ $options.i18n.settings }}</gl-dropdown-section-header>
+ <div v-if="hasDropdownActions" class="gl-p-2">
+ <gl-dropdown
+ data-testid="dropdown-toggle"
+ right
+ :text="$options.i18n.userAdministration"
+ :text-sr-only="!showButtonLabels"
+ icon="settings"
+ data-qa-selector="user_actions_dropdown_toggle"
+ :data-qa-username="user.username"
+ >
+ <gl-dropdown-section-header>{{
+ $options.i18n.userAdministration
+ }}</gl-dropdown-section-header>
- <template v-for="action in dropdownSafeActions">
- <component
- :is="getActionComponent(action)"
- v-if="getActionComponent(action)"
- :key="action"
- :path="userPaths[action]"
- :username="user.name"
- :data-testid="action"
- >
- {{ $options.i18n[action] }}
- </component>
- <gl-dropdown-item v-else-if="isLdapAction(action)" :key="action" :data-testid="action">
- {{ $options.i18n[action] }}
- </gl-dropdown-item>
- </template>
+ <template v-for="action in dropdownSafeActions">
+ <component
+ :is="getActionComponent(action)"
+ v-if="getActionComponent(action)"
+ :key="action"
+ :path="userPaths[action]"
+ :username="user.name"
+ :data-testid="action"
+ >
+ {{ $options.i18n[action] }}
+ </component>
+ <gl-dropdown-item v-else-if="isLdapAction(action)" :key="action" :data-testid="action">
+ {{ $options.i18n[action] }}
+ </gl-dropdown-item>
+ </template>
- <gl-dropdown-divider v-if="hasDeleteActions" />
+ <gl-dropdown-divider v-if="hasDeleteActions" />
- <template v-for="action in dropdownDeleteActions">
- <component
- :is="getActionComponent(action)"
- v-if="getActionComponent(action)"
- :key="action"
- :paths="userPaths"
- :username="user.name"
- :oncall-schedules="user.oncallSchedules"
- :data-testid="`delete-${action}`"
- >
- {{ $options.i18n[action] }}
- </component>
- </template>
- </gl-dropdown>
+ <template v-for="action in dropdownDeleteActions">
+ <component
+ :is="getActionComponent(action)"
+ v-if="getActionComponent(action)"
+ :key="action"
+ :paths="userPaths"
+ :username="user.name"
+ :oncall-schedules="user.oncallSchedules"
+ :data-testid="`delete-${action}`"
+ >
+ {{ $options.i18n[action] }}
+ </component>
+ </template>
+ </gl-dropdown>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js
index c55edefe607..4636c8705a5 100644
--- a/app/assets/javascripts/admin/users/constants.js
+++ b/app/assets/javascripts/admin/users/constants.js
@@ -6,7 +6,7 @@ export const LENGTH_OF_USER_NOTE_TOOLTIP = 100;
export const I18N_USER_ACTIONS = {
edit: __('Edit'),
- settings: __('Settings'),
+ userAdministration: s__('AdminUsers|User administration'),
unlock: __('Unlock'),
block: s__('AdminUsers|Block'),
unblock: s__('AdminUsers|Unblock'),
@@ -17,4 +17,12 @@ export const I18N_USER_ACTIONS = {
ldapBlocked: s__('AdminUsers|Cannot unblock LDAP blocked users'),
delete: s__('AdminUsers|Delete user'),
deleteWithContributions: s__('AdminUsers|Delete user and contributions'),
+ 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..852b253d25a 100644
--- a/app/assets/javascripts/admin/users/index.js
+++ b/app/assets/javascripts/admin/users/index.js
@@ -2,7 +2,15 @@ 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 UserActions from './components/user_actions.vue';
+import {
+ CONFIRM_DELETE_BUTTON_SELECTOR,
+ MODAL_TEXTS_CONTAINER_SELECTOR,
+ MODAL_MANAGER_SELECTOR,
+} from './constants';
Vue.use(VueApollo);
@@ -10,22 +18,71 @@ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
});
-export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-app')) => {
+const initApp = (el, component, userPropKey, props = {}) => {
if (!el) {
return false;
}
- const { users, paths } = el.dataset;
+ const { [userPropKey]: user, paths } = el.dataset;
return new Vue({
el,
apolloProvider,
render: (createElement) =>
- createElement(AdminUsersApp, {
+ createElement(component, {
props: {
- users: convertObjectPropsToCamelCase(JSON.parse(users), { deep: true }),
+ [userPropKey]: convertObjectPropsToCamelCase(JSON.parse(user), { deep: true }),
paths: convertObjectPropsToCamelCase(JSON.parse(paths)),
+ ...props,
},
}),
});
};
+
+export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-app')) =>
+ initApp(el, AdminUsersApp, 'users');
+
+export const initAdminUserActions = (el = document.querySelector('#js-admin-user-actions')) =>
+ initApp(el, UserActions, 'user', { showButtonLabels: true });
+
+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,
+ },
+ });
+ },
+ });
+};