2022-03-07 21:18:32 +00:00
|
|
|
<script>
|
2022-05-17 15:09:01 +00:00
|
|
|
import {
|
|
|
|
GlAlert,
|
|
|
|
GlButton,
|
|
|
|
GlIcon,
|
|
|
|
GlLink,
|
|
|
|
GlLoadingIcon,
|
|
|
|
GlModal,
|
|
|
|
GlModalDirective,
|
|
|
|
GlPagination,
|
|
|
|
GlSprintf,
|
|
|
|
GlTable,
|
|
|
|
GlTooltipDirective,
|
|
|
|
} from '@gitlab/ui';
|
|
|
|
import * as Sentry from '@sentry/browser';
|
2022-03-07 21:18:32 +00:00
|
|
|
import Api, { DEFAULT_PER_PAGE } from '~/api';
|
|
|
|
import { helpPagePath } from '~/helpers/help_page_helper';
|
2022-05-17 15:09:01 +00:00
|
|
|
import httpStatusCodes from '~/lib/utils/http_status';
|
|
|
|
import { __, s__, sprintf } from '~/locale';
|
2022-06-27 21:08:29 +00:00
|
|
|
import Tracking from '~/tracking';
|
2022-03-07 21:18:32 +00:00
|
|
|
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
|
|
|
|
|
|
|
export default {
|
|
|
|
components: {
|
2022-05-17 15:09:01 +00:00
|
|
|
GlAlert,
|
|
|
|
GlButton,
|
|
|
|
GlIcon,
|
2022-03-07 21:18:32 +00:00
|
|
|
GlLink,
|
|
|
|
GlLoadingIcon,
|
2022-05-17 15:09:01 +00:00
|
|
|
GlModal,
|
2022-03-07 21:18:32 +00:00
|
|
|
GlPagination,
|
2022-05-17 15:09:01 +00:00
|
|
|
GlSprintf,
|
2022-03-07 21:18:32 +00:00
|
|
|
GlTable,
|
|
|
|
TimeagoTooltip,
|
|
|
|
},
|
2022-05-17 15:09:01 +00:00
|
|
|
directives: {
|
|
|
|
GlTooltip: GlTooltipDirective,
|
|
|
|
GlModal: GlModalDirective,
|
|
|
|
},
|
2022-06-27 21:08:29 +00:00
|
|
|
mixins: [Tracking.mixin()],
|
2022-05-17 15:09:01 +00:00
|
|
|
inject: ['projectId', 'admin', 'fileSizeLimit'],
|
2022-03-07 21:18:32 +00:00
|
|
|
docsLink: helpPagePath('ci/secure_files/index'),
|
|
|
|
DEFAULT_PER_PAGE,
|
|
|
|
i18n: {
|
2022-05-17 15:09:01 +00:00
|
|
|
deleteLabel: __('Delete File'),
|
|
|
|
uploadLabel: __('Upload File'),
|
|
|
|
uploadingLabel: __('Uploading...'),
|
2022-03-07 21:18:32 +00:00
|
|
|
pagination: {
|
|
|
|
next: __('Next'),
|
|
|
|
prev: __('Prev'),
|
|
|
|
},
|
|
|
|
title: __('Secure Files'),
|
|
|
|
overviewMessage: __(
|
|
|
|
'Use Secure Files to store files used by your pipelines such as Android keystores, or Apple provisioning profiles and signing certificates.',
|
|
|
|
),
|
|
|
|
moreInformation: __('More information'),
|
2022-05-17 15:09:01 +00:00
|
|
|
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'),
|
2022-03-07 21:18:32 +00:00
|
|
|
},
|
2022-05-17 15:09:01 +00:00
|
|
|
deleteModalId: 'deleteModalId',
|
2022-03-07 21:18:32 +00:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
page: 1,
|
|
|
|
totalItems: 0,
|
|
|
|
loading: false,
|
2022-05-17 15:09:01 +00:00
|
|
|
uploading: false,
|
|
|
|
error: false,
|
|
|
|
errorMessage: null,
|
2022-03-07 21:18:32 +00:00
|
|
|
projectSecureFiles: [],
|
2022-05-17 15:09:01 +00:00
|
|
|
deleteModalFileId: null,
|
|
|
|
deleteModalFileName: null,
|
2022-03-07 21:18:32 +00:00
|
|
|
};
|
|
|
|
},
|
|
|
|
fields: [
|
|
|
|
{
|
|
|
|
key: 'name',
|
|
|
|
label: __('Filename'),
|
2022-05-17 15:09:01 +00:00
|
|
|
tdClass: 'gl-vertical-align-middle!',
|
2022-03-07 21:18:32 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
key: 'created_at',
|
|
|
|
label: __('Uploaded'),
|
2022-05-17 15:09:01 +00:00
|
|
|
tdClass: 'gl-vertical-align-middle!',
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: 'actions',
|
|
|
|
label: '',
|
|
|
|
tdClass: 'gl-text-right gl-vertical-align-middle!',
|
2022-03-07 21:18:32 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
computed: {
|
|
|
|
fields() {
|
|
|
|
return this.$options.fields;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
page(newPage) {
|
|
|
|
this.getProjectSecureFiles(newPage);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
created() {
|
|
|
|
this.getProjectSecureFiles();
|
|
|
|
},
|
|
|
|
methods: {
|
2022-05-17 15:09:01 +00:00
|
|
|
async deleteSecureFile(secureFileId) {
|
|
|
|
this.loading = true;
|
|
|
|
this.error = false;
|
|
|
|
try {
|
|
|
|
await Api.deleteProjectSecureFile(this.projectId, secureFileId);
|
|
|
|
this.getProjectSecureFiles();
|
2022-06-27 21:08:29 +00:00
|
|
|
|
|
|
|
this.track('delete_secure_file');
|
2022-05-17 15:09:01 +00:00
|
|
|
} catch (error) {
|
|
|
|
Sentry.captureException(error);
|
|
|
|
this.error = true;
|
|
|
|
this.errorMessage = error;
|
|
|
|
}
|
|
|
|
},
|
2022-03-07 21:18:32 +00:00
|
|
|
async getProjectSecureFiles(page) {
|
|
|
|
this.loading = true;
|
|
|
|
const response = await Api.projectSecureFiles(this.projectId, { page });
|
|
|
|
|
|
|
|
this.totalItems = parseInt(response.headers?.['x-total'], 10) || 0;
|
|
|
|
|
|
|
|
this.projectSecureFiles = response.data;
|
|
|
|
|
|
|
|
this.loading = false;
|
2022-05-17 15:09:01 +00:00
|
|
|
this.uploading = false;
|
2022-06-27 21:08:29 +00:00
|
|
|
this.track('render_secure_files_list');
|
2022-05-17 15:09:01 +00:00
|
|
|
},
|
|
|
|
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();
|
2022-06-27 21:08:29 +00:00
|
|
|
this.track('upload_secure_file');
|
2022-05-17 15:09:01 +00:00
|
|
|
} 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;
|
2022-03-07 21:18:32 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<div>
|
2022-05-17 15:09:01 +00:00
|
|
|
<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">
|
2022-05-26 09:08:11 +00:00
|
|
|
<gl-button v-if="admin" class="gl-mt-3" variant="confirm" @click="loadFileSelctor">
|
2022-05-17 15:09:01 +00:00
|
|
|
<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>
|
2022-03-07 21:18:32 +00:00
|
|
|
|
2022-05-17 15:09:01 +00:00
|
|
|
<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>
|
2022-03-07 21:18:32 +00:00
|
|
|
|
|
|
|
<gl-table
|
|
|
|
:busy="loading"
|
|
|
|
:fields="fields"
|
|
|
|
:items="projectSecureFiles"
|
|
|
|
tbody-tr-class="js-ci-secure-files-row"
|
|
|
|
data-qa-selector="ci_secure_files_table_content"
|
|
|
|
sort-by="key"
|
|
|
|
sort-direction="asc"
|
|
|
|
stacked="lg"
|
|
|
|
table-class="text-secondary"
|
|
|
|
show-empty
|
|
|
|
sort-icon-left
|
|
|
|
no-sort-reset
|
|
|
|
>
|
|
|
|
<template #table-busy>
|
|
|
|
<gl-loading-icon size="lg" class="gl-my-5" />
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<template #cell(name)="{ item }">
|
|
|
|
{{ item.name }}
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<template #cell(created_at)="{ item }">
|
|
|
|
<timeago-tooltip :time="item.created_at" />
|
|
|
|
</template>
|
2022-05-17 15:09:01 +00:00
|
|
|
|
|
|
|
<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>
|
2022-03-07 21:18:32 +00:00
|
|
|
</gl-table>
|
2022-05-17 15:09:01 +00:00
|
|
|
|
2022-03-07 21:18:32 +00:00
|
|
|
<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"
|
|
|
|
/>
|
2022-05-17 15:09:01 +00:00
|
|
|
|
|
|
|
<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>
|
2022-03-07 21:18:32 +00:00
|
|
|
</div>
|
|
|
|
</template>
|