diff options
Diffstat (limited to 'app/assets/javascripts/admin')
12 files changed, 311 insertions, 92 deletions
diff --git a/app/assets/javascripts/admin/deploy_keys/components/table.vue b/app/assets/javascripts/admin/deploy_keys/components/table.vue index 97a5a2f2f32..29e8b9a724e 100644 --- a/app/assets/javascripts/admin/deploy_keys/components/table.vue +++ b/app/assets/javascripts/admin/deploy_keys/components/table.vue @@ -1,13 +1,33 @@ <script> -import { GlTable, GlButton } from '@gitlab/ui'; +import { GlTable, GlButton, GlPagination, GlLoadingIcon, GlEmptyState, GlModal } from '@gitlab/ui'; import { __ } from '~/locale'; +import Api, { DEFAULT_PER_PAGE } from '~/api'; +import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { cleanLeadingSeparator } from '~/lib/utils/url_utility'; +import createFlash from '~/flash'; +import csrf from '~/lib/utils/csrf'; export default { name: 'DeployKeysTable', i18n: { pageTitle: __('Public deploy keys'), newDeployKeyButtonText: __('New deploy key'), + emptyStateTitle: __('No public deploy keys'), + emptyStateDescription: __( + 'Deploy keys grant read/write access to all repositories in your instance', + ), + delete: __('Delete deploy key'), + edit: __('Edit deploy key'), + pagination: { + next: __('Next'), + prev: __('Prev'), + }, + modal: { + title: __('Are you sure?'), + body: __('Are you sure you want to delete this deploy key?'), + }, + apiErrorMessage: __('An error occurred fetching the public deploy keys. Please try again.'), }, fields: [ { @@ -29,13 +49,118 @@ export default { { key: 'actions', label: __('Actions'), + tdClass: 'gl-lg-w-1px gl-white-space-nowrap', + thClass: 'gl-lg-w-1px gl-white-space-nowrap', }, ], + modal: { + id: 'delete-deploy-key-modal', + actionPrimary: { + text: __('Delete'), + attributes: { + variant: 'danger', + }, + }, + actionSecondary: { + text: __('Cancel'), + attributes: { + variant: 'default', + }, + }, + }, + csrf, + DEFAULT_PER_PAGE, components: { GlTable, GlButton, + GlPagination, + TimeAgoTooltip, + GlLoadingIcon, + GlEmptyState, + GlModal, }, inject: ['editPath', 'deletePath', 'createPath', 'emptyStateSvgPath'], + data() { + return { + page: 1, + totalItems: 0, + loading: false, + items: [], + deployKeyToDelete: null, + }; + }, + computed: { + shouldShowTable() { + return this.totalItems !== 0 || this.loading; + }, + isModalVisible() { + return this.deployKeyToDelete !== null; + }, + deleteAction() { + return this.deployKeyToDelete === null + ? null + : this.deletePath.replace(':id', this.deployKeyToDelete); + }, + }, + watch: { + page(newPage) { + this.fetchDeployKeys(newPage); + }, + }, + mounted() { + this.fetchDeployKeys(); + }, + methods: { + editHref(id) { + return this.editPath.replace(':id', id); + }, + projectHref(project) { + return `/${cleanLeadingSeparator(project.path_with_namespace)}`; + }, + async fetchDeployKeys(page) { + this.loading = true; + try { + const { headers, data: items } = await Api.deployKeys({ + page, + public: true, + }); + + if (this.totalItems === 0) { + this.totalItems = parseInt(headers?.['x-total'], 10) || 0; + } + + this.items = items.map( + ({ id, title, fingerprint, projects_with_write_access, created_at }) => ({ + id, + title, + fingerprint, + projects: projects_with_write_access, + created: created_at, + }), + ); + } catch (error) { + createFlash({ + message: this.$options.i18n.apiErrorMessage, + captureError: true, + error, + }); + + this.totalItems = 0; + + this.items = []; + } + this.loading = false; + }, + handleDeleteClick(id) { + this.deployKeyToDelete = id; + }, + handleModalHide() { + this.deployKeyToDelete = null; + }, + handleModalPrimary() { + this.$refs.modalForm.submit(); + }, + }, }; </script> @@ -45,10 +170,92 @@ export default { <h4 class="gl-m-0"> {{ $options.i18n.pageTitle }} </h4> - <gl-button variant="confirm" :href="createPath">{{ + <gl-button variant="confirm" :href="createPath" data-testid="new-deploy-key-button">{{ $options.i18n.newDeployKeyButtonText }}</gl-button> </div> - <gl-table :fields="$options.fields" data-testid="deploy-keys-list" /> + <template v-if="shouldShowTable"> + <gl-table + :busy="loading" + :items="items" + :fields="$options.fields" + stacked="lg" + data-testid="deploy-keys-list" + > + <template #table-busy> + <gl-loading-icon size="lg" class="gl-my-5" /> + </template> + + <template #cell(projects)="{ item: { projects } }"> + <a + v-for="project in projects" + :key="project.id" + :href="projectHref(project)" + class="gl-display-block" + >{{ project.name_with_namespace }}</a + > + </template> + + <template #cell(fingerprint)="{ item: { fingerprint } }"> + <code>{{ fingerprint }}</code> + </template> + + <template #cell(created)="{ item: { created } }"> + <time-ago-tooltip :time="created" /> + </template> + + <template #head(actions)="{ label }"> + <span class="gl-sr-only">{{ label }}</span> + </template> + + <template #cell(actions)="{ item: { id } }"> + <gl-button + icon="pencil" + :aria-label="$options.i18n.edit" + :href="editHref(id)" + class="gl-mr-2" + /> + <gl-button + variant="danger" + icon="remove" + :aria-label="$options.i18n.delete" + @click="handleDeleteClick(id)" + /> + </template> + </gl-table> + <gl-pagination + v-if="!loading" + v-model="page" + :per-page="$options.DEFAULT_PER_PAGE" + :total-items="totalItems" + :next-text="$options.i18n.pagination.next" + :prev-text="$options.i18n.pagination.prev" + align="center" + /> + </template> + <gl-empty-state + v-else + :svg-path="emptyStateSvgPath" + :title="$options.i18n.emptyStateTitle" + :description="$options.i18n.emptyStateDescription" + :primary-button-text="$options.i18n.newDeployKeyButtonText" + :primary-button-link="createPath" + /> + <gl-modal + :modal-id="$options.modal.id" + :visible="isModalVisible" + :title="$options.i18n.modal.title" + :action-primary="$options.modal.actionPrimary" + :action-secondary="$options.modal.actionSecondary" + size="sm" + @hide="handleModalHide" + @primary="handleModalPrimary" + > + <form ref="modalForm" :action="deleteAction" method="post"> + <input type="hidden" name="_method" value="delete" /> + <input type="hidden" name="authenticity_token" :value="$options.csrf.token" /> + </form> + {{ $options.i18n.modal.body }} + </gl-modal> </div> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/activate.vue b/app/assets/javascripts/admin/users/components/actions/activate.vue index 74e9c60a57b..3a54035c587 100644 --- a/app/assets/javascripts/admin/users/components/actions/activate.vue +++ b/app/assets/javascripts/admin/users/components/actions/activate.vue @@ -1,6 +1,7 @@ <script> import { GlDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; +import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 @@ -26,16 +27,15 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Activate user %{username}?'), { username: this.username, }), - messageHtml, actionCancel: { text: __('Cancel'), }, @@ -43,15 +43,16 @@ export default { text: I18N_USER_ACTIONS.activate, attributes: [{ variant: 'confirm' }], }, - }), - }; + messageHtml, + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <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 77a9be8eec2..5a8c675822d 100644 --- a/app/assets/javascripts/admin/users/components/actions/approve.vue +++ b/app/assets/javascripts/admin/users/components/actions/approve.vue @@ -1,6 +1,7 @@ <script> import { GlDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; +import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 @@ -28,12 +29,12 @@ export default { required: true, }, }, - computed: { - attributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Approve user %{username}?'), { username: this.username, }), @@ -45,16 +46,15 @@ export default { attributes: [{ variant: 'confirm', 'data-qa-selector': 'approve_user_confirm_button' }], }, messageHtml, - }), - 'data-qa-selector': 'approve_user_button', - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...attributes }"> + <gl-dropdown-item data-qa-selector="approve_user_button" @click="onClick"> <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 index e5ab0f9123f..55938832dce 100644 --- a/app/assets/javascripts/admin/users/components/actions/ban.vue +++ b/app/assets/javascripts/admin/users/components/actions/ban.vue @@ -2,6 +2,7 @@ import { GlDropdownItem } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { sprintf, s__, __ } from '~/locale'; +import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 @@ -39,12 +40,12 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Ban user %{username}?'), { username: this.username, }), @@ -56,15 +57,15 @@ export default { attributes: [{ variant: 'confirm' }], }, messageHtml, - }), - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <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 03557008a89..d25dd400f9b 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 eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 @@ -29,12 +30,12 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Block user %{username}?'), { username: this.username }), actionCancel: { text: __('Cancel'), @@ -44,15 +45,15 @@ export default { attributes: [{ variant: 'confirm' }], }, messageHtml, - }), - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <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 640c8fefc20..c85f3f01675 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 eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 @@ -36,12 +37,12 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Deactivate user %{username}?'), { username: this.username, }), @@ -53,15 +54,15 @@ export default { attributes: [{ variant: 'confirm' }], }, messageHtml, - }), - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <slot></slot> </gl-dropdown-item> </template> diff --git a/app/assets/javascripts/admin/users/components/actions/reject.vue b/app/assets/javascripts/admin/users/components/actions/reject.vue index 901306455fa..bac08de1d5e 100644 --- a/app/assets/javascripts/admin/users/components/actions/reject.vue +++ b/app/assets/javascripts/admin/users/components/actions/reject.vue @@ -2,6 +2,7 @@ import { GlDropdownItem } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { sprintf, s__, __ } from '~/locale'; +import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 @@ -39,12 +40,12 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'delete', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'delete', + modalAttributes: { title: sprintf(s__('AdminUsers|Reject user %{username}?'), { username: this.username, }), @@ -56,15 +57,15 @@ export default { attributes: [{ variant: 'danger' }], }, messageHtml, - }), - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <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 index 8083e26177e..beede2d37d7 100644 --- a/app/assets/javascripts/admin/users/components/actions/unban.vue +++ b/app/assets/javascripts/admin/users/components/actions/unban.vue @@ -1,6 +1,7 @@ <script> import { GlDropdownItem } from '@gitlab/ui'; import { sprintf, s__, __ } from '~/locale'; +import eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 @@ -22,12 +23,12 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Unban user %{username}?'), { username: this.username, }), @@ -39,15 +40,15 @@ export default { attributes: [{ variant: 'confirm' }], }, messageHtml, - }), - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <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 7de6653e0cd..720f2efd932 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 eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; export default { @@ -17,12 +18,13 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Unblock user %{username}?'), { username: this.username }), message: s__('AdminUsers|You can always block their account again if needed.'), actionCancel: { @@ -32,15 +34,15 @@ export default { text: I18N_USER_ACTIONS.unblock, attributes: [{ variant: 'confirm' }], }, - }), - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <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 10d4fb06d61..55ea3e0aba7 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 eventHub, { EVENT_OPEN_CONFIRM_MODAL } from '~/vue_shared/components/confirm_modal_eventhub'; import { I18N_USER_ACTIONS } from '../../constants'; export default { @@ -17,12 +18,12 @@ export default { required: true, }, }, - computed: { - modalAttributes() { - return { - 'data-path': this.path, - 'data-method': 'put', - 'data-modal-attributes': JSON.stringify({ + methods: { + onClick() { + eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, { + path: this.path, + method: 'put', + modalAttributes: { title: sprintf(s__('AdminUsers|Unlock user %{username}?'), { username: this.username }), message: __('Are you sure?'), actionCancel: { @@ -32,15 +33,15 @@ export default { text: I18N_USER_ACTIONS.unlock, attributes: [{ variant: 'confirm' }], }, - }), - }; + }, + }); }, }, }; </script> <template> - <gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item @click="onClick"> <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 index e949498c55b..d7c08096376 100644 --- a/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue +++ b/app/assets/javascripts/admin/users/components/modals/delete_user_modal.vue @@ -57,14 +57,17 @@ export default { }; }, computed: { + trimmedUsername() { + return this.username.trim(); + }, modalTitle() { - return sprintf(this.title, { username: this.username }, false); + return sprintf(this.title, { username: this.trimmedUsername }, false); }, secondaryButtonLabel() { return s__('AdminUsers|Block user'); }, canSubmit() { - return this.enteredUsername === this.username; + return this.enteredUsername === this.trimmedUsername; }, obstacles() { try { @@ -104,7 +107,7 @@ export default { <p> <gl-sprintf :message="content"> <template #username> - <strong>{{ username }}</strong> + <strong>{{ trimmedUsername }}</strong> </template> <template #strong="props"> <strong>{{ props.content }}</strong> @@ -115,13 +118,13 @@ export default { <user-deletion-obstacles-list v-if="obstacles.length" :obstacles="obstacles" - :user-name="username" + :user-name="trimmedUsername" /> <p> <gl-sprintf :message="s__('AdminUsers|To confirm, type %{username}')"> <template #username> - <code class="gl-white-space-pre-wrap">{{ username }}</code> + <code class="gl-white-space-pre-wrap">{{ trimmedUsername }}</code> </template> </gl-sprintf> </p> diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue index 4f4e2947341..567d7151847 100644 --- a/app/assets/javascripts/admin/users/components/user_actions.vue +++ b/app/assets/javascripts/admin/users/components/user_actions.vue @@ -112,7 +112,7 @@ export default { right :text="$options.i18n.userAdministration" :text-sr-only="!showButtonLabels" - icon="settings" + icon="ellipsis_h" data-qa-selector="user_actions_dropdown_toggle" :data-qa-username="user.username" > |