diff options
Diffstat (limited to 'app/assets/javascripts/ci_secure_files/components/secure_files_list.vue')
-rw-r--r-- | app/assets/javascripts/ci_secure_files/components/secure_files_list.vue | 202 |
1 files changed, 182 insertions, 20 deletions
diff --git a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue index d70ade36fe9..dbc4565b19d 100644 --- a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue +++ b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue @@ -1,22 +1,48 @@ <script> -import { GlLink, GlLoadingIcon, GlPagination, GlTable } from '@gitlab/ui'; +import { + GlAlert, + GlButton, + GlIcon, + GlLink, + GlLoadingIcon, + GlModal, + GlModalDirective, + GlPagination, + GlSprintf, + GlTable, + GlTooltipDirective, +} from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; import Api, { DEFAULT_PER_PAGE } from '~/api'; import { helpPagePath } from '~/helpers/help_page_helper'; -import { __ } from '~/locale'; +import httpStatusCodes from '~/lib/utils/http_status'; +import { __, s__, sprintf } from '~/locale'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; export default { components: { + GlAlert, + GlButton, + GlIcon, GlLink, GlLoadingIcon, + GlModal, GlPagination, + GlSprintf, GlTable, TimeagoTooltip, }, - inject: ['projectId'], + directives: { + GlTooltip: GlTooltipDirective, + GlModal: GlModalDirective, + }, + inject: ['projectId', 'admin', 'fileSizeLimit'], docsLink: helpPagePath('ci/secure_files/index'), DEFAULT_PER_PAGE, i18n: { + deleteLabel: __('Delete File'), + uploadLabel: __('Upload File'), + uploadingLabel: __('Uploading...'), pagination: { next: __('Next'), prev: __('Prev'), @@ -26,27 +52,45 @@ export default { 'Use Secure Files to store files used by your pipelines such as Android keystores, or Apple provisioning profiles and signing certificates.', ), moreInformation: __('More information'), + uploadErrorMessages: { + duplicate: __('A file with this name already exists.'), + tooLarge: __('File too large. Secure Files must be less than %{limit} MB.'), + }, + deleteModalTitle: s__('SecureFiles|Delete %{name}?'), + deleteModalMessage: s__( + 'SecureFiles|Secure File %{name} will be permanently deleted. Are you sure?', + ), + deleteModalButton: s__('SecureFiles|Delete secure file'), }, + deleteModalId: 'deleteModalId', data() { return { page: 1, totalItems: 0, loading: false, + uploading: false, + error: false, + errorMessage: null, projectSecureFiles: [], + deleteModalFileId: null, + deleteModalFileName: null, }; }, fields: [ { key: 'name', label: __('Filename'), - }, - { - key: 'permissions', - label: __('Permissions'), + tdClass: 'gl-vertical-align-middle!', }, { key: 'created_at', label: __('Uploaded'), + tdClass: 'gl-vertical-align-middle!', + }, + { + key: 'actions', + label: '', + tdClass: 'gl-text-right gl-vertical-align-middle!', }, ], computed: { @@ -63,6 +107,18 @@ export default { this.getProjectSecureFiles(); }, methods: { + async deleteSecureFile(secureFileId) { + this.loading = true; + this.error = false; + try { + await Api.deleteProjectSecureFile(this.projectId, secureFileId); + this.getProjectSecureFiles(); + } catch (error) { + Sentry.captureException(error); + this.error = true; + this.errorMessage = error; + } + }, async getProjectSecureFiles(page) { this.loading = true; const response = await Api.projectSecureFiles(this.projectId, { page }); @@ -72,6 +128,48 @@ export default { this.projectSecureFiles = response.data; this.loading = false; + this.uploading = false; + }, + async uploadSecureFile() { + this.error = null; + this.uploading = true; + const [file] = this.$refs.fileUpload.files; + try { + await Api.uploadProjectSecureFile(this.projectId, this.uploadFormData(file)); + this.getProjectSecureFiles(); + } catch (error) { + this.error = true; + this.errorMessage = this.formattedErrorMessage(error); + this.uploading = false; + } + }, + formattedErrorMessage(error) { + let message = ''; + if (error?.response?.data?.message?.name) { + message = this.$options.i18n.uploadErrorMessages.duplicate; + } else if (error.response.status === httpStatusCodes.PAYLOAD_TOO_LARGE) { + message = sprintf(this.$options.i18n.uploadErrorMessages.tooLarge, { + limit: this.fileSizeLimit, + }); + } else { + Sentry.captureException(error); + message = error; + } + return message; + }, + loadFileSelctor() { + this.$refs.fileUpload.click(); + }, + setDeleteModalData(secureFile) { + this.deleteModalFileId = secureFile.id; + this.deleteModalFileName = secureFile.name; + }, + uploadFormData(file) { + const formData = new FormData(); + formData.append('name', file.name); + formData.append('file', file); + + return formData; }, }, }; @@ -79,16 +177,51 @@ export default { <template> <div> - <h1 data-testid="title" class="gl-font-size-h1 gl-mt-3 gl-mb-0">{{ $options.i18n.title }}</h1> + <gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="error = null"> + {{ errorMessage }} + </gl-alert> + <div class="row"> + <div class="col-md-12 col-lg-6 gl-display-flex"> + <div class="gl-flex-direction-column gl-flex-wrap"> + <h1 class="gl-font-size-h1 gl-mt-3 gl-mb-0"> + {{ $options.i18n.title }} + </h1> + </div> + </div> + + <div class="col-md-12 col-lg-6"> + <div class="gl-display-flex gl-flex-wrap gl-justify-content-end"> + <gl-button v-if="admin" class="gl-mt-3" variant="info" @click="loadFileSelctor"> + <span v-if="uploading"> + <gl-loading-icon size="sm" class="gl-my-5" inline /> + {{ $options.i18n.uploadingLabel }} + </span> + <span v-else> + <gl-icon name="upload" class="gl-mr-2" /> {{ $options.i18n.uploadLabel }} + </span> + </gl-button> + <input + id="file-upload" + ref="fileUpload" + type="file" + class="hidden" + data-qa-selector="file_upload_field" + @change="uploadSecureFile" + /> + </div> + </div> + </div> - <p> - <span data-testid="info-message" class="gl-mr-2"> - {{ $options.i18n.overviewMessage }} - <gl-link :href="$options.docsLink" target="_blank">{{ - $options.i18n.moreInformation - }}</gl-link> - </span> - </p> + <div class="row"> + <div class="col-md-12 col-lg-12 gl-my-4"> + <span data-testid="info-message"> + {{ $options.i18n.overviewMessage }} + <gl-link :href="$options.docsLink" target="_blank">{{ + $options.i18n.moreInformation + }}</gl-link> + </span> + </div> + </div> <gl-table :busy="loading" @@ -112,14 +245,23 @@ export default { {{ item.name }} </template> - <template #cell(permissions)="{ item }"> - {{ item.permissions }} - </template> - <template #cell(created_at)="{ item }"> <timeago-tooltip :time="item.created_at" /> </template> + + <template #cell(actions)="{ item }"> + <gl-button + v-if="admin" + v-gl-modal="$options.deleteModalId" + v-gl-tooltip.hover.top="$options.i18n.deleteLabel" + variant="danger" + icon="remove" + :aria-label="$options.i18n.deleteLabel" + @click="setDeleteModalData(item)" + /> + </template> </gl-table> + <gl-pagination v-if="!loading" v-model="page" @@ -129,5 +271,25 @@ export default { :prev-text="$options.i18n.pagination.prev" align="center" /> + + <gl-modal + :ref="$options.deleteModalId" + :modal-id="$options.deleteModalId" + title-tag="h4" + category="primary" + :ok-title="$options.i18n.deleteModalButton" + ok-variant="danger" + @ok="deleteSecureFile(deleteModalFileId)" + > + <template #modal-title> + <gl-sprintf :message="$options.i18n.deleteModalTitle"> + <template #name>{{ deleteModalFileName }}</template> + </gl-sprintf> + </template> + + <gl-sprintf :message="$options.i18n.deleteModalMessage"> + <template #name>{{ deleteModalFileName }}</template> + </gl-sprintf> + </gl-modal> </div> </template> |