diff options
Diffstat (limited to 'app/assets/javascripts/admin/deploy_keys/components/table.vue')
-rw-r--r-- | app/assets/javascripts/admin/deploy_keys/components/table.vue | 213 |
1 files changed, 210 insertions, 3 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> |