diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
commit | 9f46488805e86b1bc341ea1620b866016c2ce5ed (patch) | |
tree | f9748c7e287041e37d6da49e0a29c9511dc34768 /app/assets/javascripts/registry/explorer/pages | |
parent | dfc92d081ea0332d69c8aca2f0e745cb48ae5e6d (diff) | |
download | gitlab-ce-9f46488805e86b1bc341ea1620b866016c2ce5ed.tar.gz |
Add latest changes from gitlab-org/gitlab@13-0-stable-ee
Diffstat (limited to 'app/assets/javascripts/registry/explorer/pages')
3 files changed, 187 insertions, 192 deletions
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue index 6afd4d1107a..cc2dc531dc8 100644 --- a/app/assets/javascripts/registry/explorer/pages/details.vue +++ b/app/assets/javascripts/registry/explorer/pages/details.vue @@ -9,12 +9,14 @@ import { GlPagination, GlModal, GlSprintf, + GlAlert, + GlLink, GlEmptyState, GlResizeObserverDirective, GlSkeletonLoader, } from '@gitlab/ui'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; -import { n__, s__ } from '~/locale'; +import { n__ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import timeagoMixin from '~/vue_shared/mixins/timeago'; @@ -35,6 +37,14 @@ import { DELETE_TAG_ERROR_MESSAGE, DELETE_TAGS_SUCCESS_MESSAGE, DELETE_TAGS_ERROR_MESSAGE, + REMOVE_TAG_CONFIRMATION_TEXT, + REMOVE_TAGS_CONFIRMATION_TEXT, + DETAILS_PAGE_TITLE, + REMOVE_TAGS_BUTTON_TITLE, + REMOVE_TAG_BUTTON_TITLE, + EMPTY_IMAGE_REPOSITORY_TITLE, + EMPTY_IMAGE_REPOSITORY_MESSAGE, + ADMIN_GARBAGE_COLLECTION_TIP, } from '../constants'; export default { @@ -49,6 +59,8 @@ export default { GlSkeletonLoader, GlSprintf, GlEmptyState, + GlAlert, + GlLink, }, directives: { GlTooltip: GlTooltipDirective, @@ -60,6 +72,19 @@ export default { width: 1000, height: 40, }, + i18n: { + DETAILS_PAGE_TITLE, + REMOVE_TAGS_BUTTON_TITLE, + REMOVE_TAG_BUTTON_TITLE, + EMPTY_IMAGE_REPOSITORY_TITLE, + EMPTY_IMAGE_REPOSITORY_MESSAGE, + }, + alertMessages: { + success_tag: DELETE_TAG_SUCCESS_MESSAGE, + danger_tag: DELETE_TAG_ERROR_MESSAGE, + success_tags: DELETE_TAGS_SUCCESS_MESSAGE, + danger_tags: DELETE_TAGS_ERROR_MESSAGE, + }, data() { return { selectedItems: [], @@ -67,6 +92,7 @@ export default { selectAllChecked: false, modalDescription: null, isDesktop: true, + deleteAlertType: false, }; }, computed: { @@ -78,9 +104,15 @@ export default { }, fields() { const tagClass = this.isDesktop ? 'w-25' : ''; + const tagInnerClass = this.isDesktop ? 'mw-m' : 'gl-justify-content-end'; return [ { key: LIST_KEY_CHECKBOX, label: '', class: 'gl-w-16' }, - { key: LIST_KEY_TAG, label: LIST_LABEL_TAG, class: `${tagClass} js-tag-column` }, + { + key: LIST_KEY_TAG, + label: LIST_LABEL_TAG, + class: `${tagClass} js-tag-column`, + innerClass: tagInnerClass, + }, { key: LIST_KEY_IMAGE_ID, label: LIST_LABEL_IMAGE_ID }, { key: LIST_KEY_SIZE, label: LIST_LABEL_SIZE }, { key: LIST_KEY_LAST_UPDATED, label: LIST_LABEL_LAST_UPDATED }, @@ -110,20 +142,43 @@ export default { this.requestTagsList({ pagination: { page }, params: this.$route.params.id }); }, }, + deleteAlertConfig() { + const config = { + title: '', + message: '', + type: 'success', + }; + if (this.deleteAlertType) { + [config.type] = this.deleteAlertType.split('_'); + + const defaultMessage = this.$options.alertMessages[this.deleteAlertType]; + + if (this.config.isAdmin && config.type === 'success') { + config.title = defaultMessage; + config.message = ADMIN_GARBAGE_COLLECTION_TIP; + } else { + config.message = defaultMessage; + } + } + return config; + }, + }, + mounted() { + this.requestTagsList({ params: this.$route.params.id }); }, methods: { ...mapActions(['requestTagsList', 'requestDeleteTag', 'requestDeleteTags']), setModalDescription(itemIndex = -1) { if (itemIndex === -1) { this.modalDescription = { - message: s__(`ContainerRegistry|You are about to remove %{item} tags. Are you sure?`), + message: REMOVE_TAGS_CONFIRMATION_TEXT, item: this.itemsToBeDeleted.length, }; } else { const { path } = this.tags[itemIndex]; this.modalDescription = { - message: s__(`ContainerRegistry|You are about to remove %{item}. Are you sure?`), + message: REMOVE_TAG_CONFIRMATION_TEXT, item: path, }; } @@ -179,19 +234,17 @@ export default { this.track('click_button'); this.$refs.deleteModal.show(); }, - handleSingleDelete(itemToDelete) { + handleSingleDelete(index) { + const itemToDelete = this.tags[index]; this.itemsToBeDeleted = []; + this.selectedItems = this.selectedItems.filter(i => i !== index); return this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id }) - .then(() => - this.$toast.show(DELETE_TAG_SUCCESS_MESSAGE, { - type: 'success', - }), - ) - .catch(() => - this.$toast.show(DELETE_TAG_ERROR_MESSAGE, { - type: 'error', - }), - ); + .then(() => { + this.deleteAlertType = 'success_tag'; + }) + .catch(() => { + this.deleteAlertType = 'danger_tag'; + }); }, handleMultipleDelete() { const { itemsToBeDeleted } = this; @@ -202,24 +255,19 @@ export default { ids: itemsToBeDeleted.map(x => this.tags[x].name), params: this.$route.params.id, }) - .then(() => - this.$toast.show(DELETE_TAGS_SUCCESS_MESSAGE, { - type: 'success', - }), - ) - .catch(() => - this.$toast.show(DELETE_TAGS_ERROR_MESSAGE, { - type: 'error', - }), - ); + .then(() => { + this.deleteAlertType = 'success_tags'; + }) + .catch(() => { + this.deleteAlertType = 'danger_tags'; + }); }, onDeletionConfirmed() { this.track('confirm_delete'); if (this.isMultiDelete) { this.handleMultipleDelete(); } else { - const index = this.itemsToBeDeleted[0]; - this.handleSingleDelete(this.tags[index]); + this.handleSingleDelete(this.itemsToBeDeleted[0]); } }, handleResize() { @@ -231,9 +279,24 @@ export default { <template> <div v-gl-resize-observer="handleResize" class="my-3 w-100 slide-enter-to-element"> + <gl-alert + v-if="deleteAlertType" + :variant="deleteAlertConfig.type" + :title="deleteAlertConfig.title" + class="my-2" + @dismiss="deleteAlertType = null" + > + <gl-sprintf :message="deleteAlertConfig.message"> + <template #docLink="{content}"> + <gl-link :href="config.garbageCollectionHelpPagePath" target="_blank"> + {{ content }} + </gl-link> + </template> + </gl-sprintf> + </gl-alert> <div class="d-flex my-3 align-items-center"> <h4> - <gl-sprintf :message="s__('ContainerRegistry|%{imageName} tags')"> + <gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE"> <template #imageName> {{ imageName }} </template> @@ -256,8 +319,8 @@ export default { :disabled="!selectedItems || selectedItems.length === 0" class="float-right" variant="danger" - :title="s__('ContainerRegistry|Remove selected tags')" - :aria-label="s__('ContainerRegistry|Remove selected tags')" + :title="$options.i18n.REMOVE_TAGS_BUTTON_TITLE" + :aria-label="$options.i18n.REMOVE_TAGS_BUTTON_TITLE" @click="deleteMultipleItems()" > <gl-icon name="remove" /> @@ -272,17 +335,24 @@ export default { @change="updateSelectedItems(index)" /> </template> - <template #cell(name)="{item}"> - <span ref="rowName"> - {{ item.name }} - </span> - <clipboard-button - v-if="item.location" - ref="rowClipboardButton" - :title="item.location" - :text="item.location" - css-class="btn-default btn-transparent btn-clipboard" - /> + <template #cell(name)="{item, field}"> + <div ref="rowName" :class="[field.innerClass, 'gl-display-flex']"> + <span + v-gl-tooltip + data-testid="rowNameText" + :title="item.name" + class="gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap" + > + {{ item.name }} + </span> + <clipboard-button + v-if="item.location" + ref="rowClipboardButton" + :title="item.location" + :text="item.location" + css-class="btn-default btn-transparent btn-clipboard" + /> + </div> </template> <template #cell(short_revision)="{value}"> <span ref="rowShortRevision"> @@ -299,15 +369,15 @@ export default { </span> </template> <template #cell(created_at)="{value}"> - <span ref="rowTime"> + <span ref="rowTime" v-gl-tooltip :title="tooltipTitle(value)"> {{ timeFormatted(value) }} </span> </template> <template #cell(actions)="{index, item}"> <gl-deprecated-button ref="singleDeleteButton" - :title="s__('ContainerRegistry|Remove tag')" - :aria-label="s__('ContainerRegistry|Remove tag')" + :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE" + :aria-label="$options.i18n.REMOVE_TAG_BUTTON_TITLE" :disabled="!item.destroy_path" variant="danger" class="js-delete-registry float-right btn-inverted btn-border-color btn-icon" @@ -337,15 +407,9 @@ export default { </template> <gl-empty-state v-else - :title="s__('ContainerRegistry|This image has no active tags')" + :title="$options.i18n.EMPTY_IMAGE_REPOSITORY_TITLE" :svg-path="config.noContainersImage" - :description=" - s__( - `ContainerRegistry|The last tag related to this image was recently removed. - This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process. - If you have any questions, contact your administrator.`, - ) - " + :description="$options.i18n.EMPTY_IMAGE_REPOSITORY_MESSAGE" class="mx-auto my-0" /> </template> diff --git a/app/assets/javascripts/registry/explorer/pages/index.vue b/app/assets/javascripts/registry/explorer/pages/index.vue index 95d83c82987..709a163d56d 100644 --- a/app/assets/javascripts/registry/explorer/pages/index.vue +++ b/app/assets/javascripts/registry/explorer/pages/index.vue @@ -1,46 +1,9 @@ <script> -import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; -import { mapState, mapActions, mapGetters } from 'vuex'; -import { s__ } from '~/locale'; - -export default { - components: { - GlAlert, - GlSprintf, - GlLink, - }, - i18n: { - garbageCollectionTipText: s__( - 'ContainerRegistry|This Registry contains deleted image tag data. Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.', - ), - }, - computed: { - ...mapState(['config']), - ...mapGetters(['showGarbageCollection']), - }, - methods: { - ...mapActions(['setShowGarbageCollectionTip']), - }, -}; +export default {}; </script> <template> <div> - <gl-alert - v-if="showGarbageCollection" - variant="tip" - class="my-2" - @dismiss="setShowGarbageCollectionTip(false)" - > - <gl-sprintf :message="$options.i18n.garbageCollectionTipText"> - <template #docLink="{content}"> - <gl-link :href="config.garbageCollectionHelpPagePath" target="_blank"> - {{ content }} - </gl-link> - </template> - </gl-sprintf> - </gl-alert> - <transition name="slide"> <router-view ref="router-view" /> </transition> diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue index 8923c305b2d..4efa6f08d84 100644 --- a/app/assets/javascripts/registry/explorer/pages/list.vue +++ b/app/assets/javascripts/registry/explorer/pages/list.vue @@ -2,53 +2,52 @@ import { mapState, mapActions } from 'vuex'; import { GlEmptyState, - GlPagination, GlTooltipDirective, - GlDeprecatedButton, - GlIcon, GlModal, GlSprintf, GlLink, GlAlert, GlSkeletonLoader, + GlSearchBoxByClick, } from '@gitlab/ui'; import Tracking from '~/tracking'; -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + import ProjectEmptyState from '../components/project_empty_state.vue'; import GroupEmptyState from '../components/group_empty_state.vue'; import ProjectPolicyAlert from '../components/project_policy_alert.vue'; import QuickstartDropdown from '../components/quickstart_dropdown.vue'; +import ImageList from '../components/image_list.vue'; + import { DELETE_IMAGE_SUCCESS_MESSAGE, DELETE_IMAGE_ERROR_MESSAGE, - ASYNC_DELETE_IMAGE_ERROR_MESSAGE, CONTAINER_REGISTRY_TITLE, CONNECTION_ERROR_TITLE, CONNECTION_ERROR_MESSAGE, LIST_INTRO_TEXT, - LIST_DELETE_BUTTON_DISABLED, - REMOVE_REPOSITORY_LABEL, REMOVE_REPOSITORY_MODAL_TEXT, - ROW_SCHEDULED_FOR_DELETION, + REMOVE_REPOSITORY_LABEL, + SEARCH_PLACEHOLDER_TEXT, + IMAGE_REPOSITORY_LIST_LABEL, + EMPTY_RESULT_TITLE, + EMPTY_RESULT_MESSAGE, } from '../constants'; export default { name: 'RegistryListApp', components: { GlEmptyState, - GlPagination, ProjectEmptyState, GroupEmptyState, ProjectPolicyAlert, - ClipboardButton, QuickstartDropdown, - GlDeprecatedButton, - GlIcon, + ImageList, GlModal, GlSprintf, GlLink, GlAlert, GlSkeletonLoader, + GlSearchBoxByClick, }, directives: { GlTooltip: GlTooltipDirective, @@ -60,20 +59,23 @@ export default { height: 40, }, i18n: { - containerRegistryTitle: CONTAINER_REGISTRY_TITLE, - connectionErrorTitle: CONNECTION_ERROR_TITLE, - connectionErrorMessage: CONNECTION_ERROR_MESSAGE, - introText: LIST_INTRO_TEXT, - deleteButtonDisabled: LIST_DELETE_BUTTON_DISABLED, - removeRepositoryLabel: REMOVE_REPOSITORY_LABEL, - removeRepositoryModalText: REMOVE_REPOSITORY_MODAL_TEXT, - rowScheduledForDeletion: ROW_SCHEDULED_FOR_DELETION, - asyncDeleteErrorMessage: ASYNC_DELETE_IMAGE_ERROR_MESSAGE, + CONTAINER_REGISTRY_TITLE, + CONNECTION_ERROR_TITLE, + CONNECTION_ERROR_MESSAGE, + LIST_INTRO_TEXT, + REMOVE_REPOSITORY_MODAL_TEXT, + REMOVE_REPOSITORY_LABEL, + SEARCH_PLACEHOLDER_TEXT, + IMAGE_REPOSITORY_LIST_LABEL, + EMPTY_RESULT_TITLE, + EMPTY_RESULT_MESSAGE, }, data() { return { itemToDelete: {}, deleteAlertType: null, + search: null, + isEmpty: false, }; }, computed: { @@ -83,14 +85,6 @@ export default { label: 'registry_repository_delete', }; }, - currentPage: { - get() { - return this.pagination.page; - }, - set(page) { - this.requestImagesList({ page }); - }, - }, showQuickStartDropdown() { return Boolean(!this.isLoading && !this.config?.isGroupPage && this.images?.length); }, @@ -103,8 +97,19 @@ export default { : DELETE_IMAGE_ERROR_MESSAGE; }, }, + mounted() { + this.loadImageList(this.$route.name); + }, methods: { ...mapActions(['requestImagesList', 'requestDeleteImage']), + loadImageList(fromName) { + if (!fromName || !this.images?.length) { + return this.requestImagesList().then(() => { + this.isEmpty = this.images.length === 0; + }); + } + return Promise.resolve(); + }, deleteImage(item) { this.track('click_button'); this.itemToDelete = item; @@ -120,10 +125,6 @@ export default { this.deleteAlertType = 'danger'; }); }, - encodeListItem(item) { - const params = JSON.stringify({ name: item.path, tags_path: item.tags_path, id: item.id }); - return window.btoa(params); - }, dismissDeleteAlert() { this.deleteAlertType = null; this.itemToDelete = {}; @@ -152,12 +153,12 @@ export default { <gl-empty-state v-if="config.characterError" - :title="$options.i18n.connectionErrorTitle" + :title="$options.i18n.CONNECTION_ERROR_TITLE" :svg-path="config.containersErrorImage" > <template #description> <p> - <gl-sprintf :message="$options.i18n.connectionErrorMessage"> + <gl-sprintf :message="$options.i18n.CONNECTION_ERROR_MESSAGE"> <template #docLink="{content}"> <gl-link :href="`${config.helpPagePath}#docker-connection-error`" target="_blank"> {{ content }} @@ -171,11 +172,11 @@ export default { <template v-else> <div> <div class="d-flex justify-content-between align-items-center"> - <h4>{{ $options.i18n.containerRegistryTitle }}</h4> + <h4>{{ $options.i18n.CONTAINER_REGISTRY_TITLE }}</h4> <quickstart-dropdown v-if="showQuickStartDropdown" class="d-none d-sm-block" /> </div> <p> - <gl-sprintf :message="$options.i18n.introText"> + <gl-sprintf :message="$options.i18n.LIST_INTRO_TEXT"> <template #docLink="{content}"> <gl-link :href="config.helpPagePath" target="_blank"> {{ content }} @@ -199,73 +200,40 @@ export default { </gl-skeleton-loader> </div> <template v-else> - <div v-if="images.length" ref="imagesList" class="d-flex flex-column"> - <div - v-for="(listItem, index) in images" - :key="index" - ref="rowItem" - v-gl-tooltip="{ - placement: 'left', - disabled: !listItem.deleting, - title: $options.i18n.rowScheduledForDeletion, - }" - > - <div - class="d-flex justify-content-between align-items-center py-2 px-1 border-bottom" - :class="{ 'border-top': index === 0, 'disabled-content': listItem.deleting }" - > - <div class="d-felx align-items-center"> - <router-link - ref="detailsLink" - :to="{ name: 'details', params: { id: encodeListItem(listItem) } }" - > - {{ listItem.path }} - </router-link> - <clipboard-button - v-if="listItem.location" - ref="clipboardButton" - :disabled="listItem.deleting" - :text="listItem.location" - :title="listItem.location" - css-class="btn-default btn-transparent btn-clipboard" - /> - <gl-icon - v-if="listItem.failedDelete" - v-gl-tooltip - :title="$options.i18n.asyncDeleteErrorMessage" - name="warning" - class="text-warning align-middle" - /> - </div> - <div - v-gl-tooltip="{ disabled: listItem.destroy_path }" - class="d-none d-sm-block" - :title="$options.i18n.deleteButtonDisabled" - > - <gl-deprecated-button - ref="deleteImageButton" - v-gl-tooltip - :disabled="!listItem.destroy_path || listItem.deleting" - :title="$options.i18n.removeRepositoryLabel" - :aria-label="$options.i18n.removeRepositoryLabel" - class="btn-inverted" - variant="danger" - @click="deleteImage(listItem)" - > - <gl-icon name="remove" /> - </gl-deprecated-button> - </div> + <template v-if="!isEmpty"> + <div class="gl-display-flex gl-p-1" data-testid="listHeader"> + <div class="gl-flex-fill-1"> + <h5>{{ $options.i18n.IMAGE_REPOSITORY_LIST_LABEL }}</h5> + </div> + <div> + <gl-search-box-by-click + v-model="search" + :placeholder="$options.i18n.SEARCH_PLACEHOLDER_TEXT" + @submit="requestImagesList({ name: $event })" + /> </div> </div> - <gl-pagination - v-model="currentPage" - :per-page="pagination.perPage" - :total-items="pagination.total" - align="center" - class="w-100 mt-2" + + <image-list + v-if="images.length" + :images="images" + :pagination="pagination" + @pageChange="requestImagesList({ pagination: { page: $event }, name: search })" + @delete="deleteImage" /> - </div> + <gl-empty-state + v-else + :svg-path="config.noContainersImage" + data-testid="emptySearch" + :title="$options.i18n.EMPTY_RESULT_TITLE" + class="container-message" + > + <template #description> + {{ $options.i18n.EMPTY_RESULT_MESSAGE }} + </template> + </gl-empty-state> + </template> <template v-else> <project-empty-state v-if="!config.isGroupPage" /> <group-empty-state v-else /> @@ -279,9 +247,9 @@ export default { @ok="handleDeleteImage" @cancel="track('cancel_delete')" > - <template #modal-title>{{ $options.i18n.removeRepositoryLabel }}</template> + <template #modal-title>{{ $options.i18n.REMOVE_REPOSITORY_LABEL }}</template> <p> - <gl-sprintf :message="$options.i18n.removeRepositoryModalText"> + <gl-sprintf :message="$options.i18n.REMOVE_REPOSITORY_MODAL_TEXT"> <template #title> <b>{{ itemToDelete.path }}</b> </template> |