diff options
Diffstat (limited to 'app/assets/javascripts/registry/explorer/components')
9 files changed, 92 insertions, 64 deletions
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue index ff613daf7fa..3eeb7b29386 100644 --- a/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue +++ b/app/assets/javascripts/registry/explorer/components/details_page/details_header.vue @@ -1,15 +1,29 @@ <script> import { GlSprintf } from '@gitlab/ui'; +import { sprintf } from '~/locale'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; -import { DETAILS_PAGE_TITLE } from '../../constants/index'; +import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; +import { DETAILS_PAGE_TITLE, UPDATED_AT } from '../../constants/index'; export default { - components: { GlSprintf, TitleArea }, + components: { GlSprintf, TitleArea, MetadataItem }, + mixins: [timeagoMixin], props: { - imageName: { - type: String, - required: false, - default: '', + image: { + type: Object, + required: true, + }, + }, + computed: { + visibilityIcon() { + return this.image?.project?.visibility === 'public' ? 'eye' : 'eye-slash'; + }, + timeAgo() { + return this.timeFormatted(this.image.updatedAt); + }, + updatedText() { + return sprintf(UPDATED_AT, { time: this.timeAgo }); }, }, i18n: { @@ -23,9 +37,17 @@ export default { <template #title> <gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE"> <template #imageName> - {{ imageName }} + {{ image.name }} </template> </gl-sprintf> </template> + <template #metadata-updated> + <metadata-item + :icon="visibilityIcon" + :text="updatedText" + size="xl" + data-testid="updated-and-visibility" + /> + </template> </title-area> </template> diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue index 2844b4ffde3..ad39a898e7b 100644 --- a/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue +++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue @@ -34,7 +34,7 @@ export default { return this.tags.some(tag => this.selectedItems[tag.name]); }, showMultiDeleteButton() { - return this.tags.some(tag => tag.destroy_path) && !this.isMobile; + return this.tags.some(tag => tag.canDelete) && !this.isMobile; }, }, methods: { diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue index 2edeac1144f..5aeafd318aa 100644 --- a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue +++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue @@ -63,7 +63,7 @@ export default { }, computed: { formattedSize() { - return this.tag.total_size ? numberToHumanSize(this.tag.total_size) : NOT_AVAILABLE_SIZE; + return this.tag.totalSize ? numberToHumanSize(this.tag.totalSize) : NOT_AVAILABLE_SIZE; }, layers() { return this.tag.layers ? n__('%d layer', '%d layers', this.tag.layers) : ''; @@ -76,10 +76,10 @@ export default { return this.tag.digest?.substring(7, 14) ?? NOT_AVAILABLE_TEXT; }, publishedDate() { - return formatDate(this.tag.created_at, 'isoDate'); + return formatDate(this.tag.createdAt, 'isoDate'); }, publishedTime() { - return formatDate(this.tag.created_at, 'hh:MM Z'); + return formatDate(this.tag.createdAt, 'hh:MM Z'); }, formattedRevision() { // to be removed when API response is adjusted @@ -101,7 +101,7 @@ export default { <list-item v-bind="$attrs" :selected="selected"> <template #left-action> <gl-form-checkbox - v-if="Boolean(tag.destroy_path)" + v-if="tag.canDelete" :disabled="invalidTag" class="gl-m-0" :checked="selected" @@ -148,7 +148,7 @@ export default { <span data-testid="time"> <gl-sprintf :message="$options.i18n.CREATED_AT_LABEL"> <template #timeInfo> - <time-ago-tooltip :time="tag.created_at" /> + <time-ago-tooltip :time="tag.createdAt" /> </template> </gl-sprintf> </span> @@ -162,10 +162,10 @@ export default { </template> <template #right-action> <delete-button - :disabled="!tag.destroy_path || invalidTag" + :disabled="!tag.canDelete || invalidTag" :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE" :tooltip-title="$options.i18n.REMOVE_TAG_BUTTON_DISABLE_TOOLTIP" - :tooltip-disabled="Boolean(tag.destroy_path)" + :tooltip-disabled="tag.canDelete" data-testid="single-delete-button" @delete="$emit('delete')" /> diff --git a/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue b/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue index ba55822f0ca..319666210d6 100644 --- a/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue +++ b/app/assets/javascripts/registry/explorer/components/list_page/cli_commands.vue @@ -1,6 +1,5 @@ <script> import { GlDropdown } from '@gitlab/ui'; -import { mapGetters } from 'vuex'; import Tracking from '~/tracking'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; import { @@ -20,6 +19,7 @@ export default { GlDropdown, CodeInstruction, }, + inject: ['config', 'dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand'], mixins: [Tracking.mixin({ label: trackingLabel })], trackingLabel, i18n: { @@ -31,9 +31,6 @@ export default { PUSH_COMMAND_LABEL, COPY_PUSH_TITLE, }, - computed: { - ...mapGetters(['dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand']), - }, }; </script> <template> diff --git a/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue b/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue index 80cc392f86a..26e9fee63af 100644 --- a/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue +++ b/app/assets/javascripts/registry/explorer/components/list_page/group_empty_state.vue @@ -1,17 +1,14 @@ <script> import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; -import { mapState } from 'vuex'; export default { name: 'GroupEmptyState', + inject: ['config'], components: { GlEmptyState, GlSprintf, GlLink, }, - computed: { - ...mapState(['config']), - }, }; </script> <template> diff --git a/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue b/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue index d1b9894da0e..f8b3233438f 100644 --- a/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue +++ b/app/assets/javascripts/registry/explorer/components/list_page/image_list.vue @@ -1,11 +1,11 @@ <script> -import { GlPagination } from '@gitlab/ui'; +import { GlKeysetPagination } from '@gitlab/ui'; import ImageListRow from './image_list_row.vue'; export default { name: 'ImageList', components: { - GlPagination, + GlKeysetPagination, ImageListRow, }, props: { @@ -13,19 +13,14 @@ export default { type: Array, required: true, }, - pagination: { + pageInfo: { type: Object, required: true, }, }, computed: { - currentPage: { - get() { - return this.pagination.page; - }, - set(page) { - this.$emit('pageChange', page); - }, + showPagination() { + return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage; }, }, }; @@ -40,13 +35,15 @@ export default { :first="index === 0" @delete="$emit('delete', $event)" /> - - <gl-pagination - v-model="currentPage" - :per-page="pagination.perPage" - :total-items="pagination.total" - align="center" - class="w-100 gl-mt-3" - /> + <div class="gl-display-flex gl-justify-content-center"> + <gl-keyset-pagination + v-if="showPagination" + :has-next-page="pageInfo.hasNextPage" + :has-previous-page="pageInfo.hasPreviousPage" + class="gl-mt-3" + @prev="$emit('prev-page')" + @next="$emit('next-page')" + /> + </div> </div> </template> diff --git a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue index b0a7c4824bd..3fe61dc231a 100644 --- a/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue +++ b/app/assets/javascripts/registry/explorer/components/list_page/image_list_row.vue @@ -1,6 +1,8 @@ <script> import { GlTooltipDirective, GlIcon, GlSprintf } from '@gitlab/ui'; import { n__ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; + import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import DeleteButton from '../delete_button.vue'; @@ -11,6 +13,8 @@ import { REMOVE_REPOSITORY_LABEL, ROW_SCHEDULED_FOR_DELETION, CLEANUP_TIMED_OUT_ERROR_MESSAGE, + IMAGE_DELETE_SCHEDULED_STATUS, + IMAGE_FAILED_DELETED_STATUS, } from '../../constants/index'; export default { @@ -38,19 +42,29 @@ export default { }, computed: { disabledDelete() { - return !this.item.destroy_path || this.item.deleting; + return !this.item.canDelete || this.deleting; + }, + id() { + return getIdFromGraphQLId(this.item.id); + }, + deleting() { + return this.item.status === IMAGE_DELETE_SCHEDULED_STATUS; + }, + failedDelete() { + return this.item.status === IMAGE_FAILED_DELETED_STATUS; }, tagsCountText() { return n__( 'ContainerRegistry|%{count} Tag', 'ContainerRegistry|%{count} Tags', - this.item.tags_count, + this.item.tagsCount, ); }, warningIconText() { - if (this.item.failedDelete) { + if (this.failedDelete) { return ASYNC_DELETE_IMAGE_ERROR_MESSAGE; - } else if (this.item.cleanup_policy_started_at) { + } + if (this.item.expirationPolicyStartedAt) { return CLEANUP_TIMED_OUT_ERROR_MESSAGE; } return null; @@ -63,23 +77,23 @@ export default { <list-item v-gl-tooltip="{ placement: 'left', - disabled: !item.deleting, + disabled: !deleting, title: $options.i18n.ROW_SCHEDULED_FOR_DELETION, }" v-bind="$attrs" - :disabled="item.deleting" + :disabled="deleting" > <template #left-primary> <router-link class="gl-text-body gl-font-weight-bold" data-testid="details-link" - :to="{ name: 'details', params: { id: item.id } }" + :to="{ name: 'details', params: { id } }" > {{ item.path }} </router-link> <clipboard-button v-if="item.location" - :disabled="item.deleting" + :disabled="deleting" :text="item.location" :title="item.location" category="tertiary" @@ -97,7 +111,7 @@ export default { <gl-icon name="tag" class="gl-mr-2" /> <gl-sprintf :message="tagsCountText"> <template #count> - {{ item.tags_count }} + {{ item.tagsCount }} </template> </gl-sprintf> </span> @@ -106,7 +120,7 @@ export default { <delete-button :title="$options.i18n.REMOVE_REPOSITORY_LABEL" :disabled="disabledDelete" - :tooltip-disabled="Boolean(item.destroy_path)" + :tooltip-disabled="item.canDelete" :tooltip-title="$options.i18n.LIST_DELETE_BUTTON_DISABLED" @delete="$emit('delete', item)" /> diff --git a/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue b/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue index 35eb0b11e40..5308b025cc0 100644 --- a/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue +++ b/app/assets/javascripts/registry/explorer/components/list_page/project_empty_state.vue @@ -1,6 +1,5 @@ <script> import { GlEmptyState, GlSprintf, GlLink, GlFormInputGroup, GlFormInput } from '@gitlab/ui'; -import { mapState, mapGetters } from 'vuex'; import { s__ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { @@ -20,6 +19,7 @@ export default { GlFormInputGroup, GlFormInput, }, + inject: ['config', 'dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand'], i18n: { quickStart: QUICK_START, copyLoginTitle: COPY_LOGIN_TITLE, @@ -35,10 +35,6 @@ export default { 'ContainerRegistry|You can add an image to this registry with the following commands:', ), }, - computed: { - ...mapState(['config']), - ...mapGetters(['dockerBuildCommand', 'dockerPushCommand', 'dockerLoginCommand']), - }, }; </script> <template> diff --git a/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue b/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue index 666d8b042da..1cedcc41b2b 100644 --- a/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue +++ b/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue @@ -1,9 +1,11 @@ <script> +/* eslint-disable vue/no-v-html */ +// We are forced to use `v-html` untill this gitlab-ui issue is resolved: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1079 +// then we can re-write this to use gl-breadcrumb import { initial, first, last } from 'lodash'; -import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { sanitize } from '~/lib/dompurify'; export default { - directives: { SafeHtml }, props: { crumbs: { type: Array, @@ -11,6 +13,9 @@ export default { }, }, computed: { + parsedCrumbs() { + return this.crumbs.map(c => ({ ...c, innerHTML: sanitize(c.innerHTML) })); + }, rootRoute() { return this.$router.options.routes.find(r => r.meta.root); }, @@ -18,11 +23,11 @@ export default { return this.$route.name === this.rootRoute.name; }, rootCrumbs() { - return initial(this.crumbs); + return initial(this.parsedCrumbs); }, divider() { const { classList, tagName, innerHTML } = first(this.crumbs).querySelector('svg'); - return { classList: [...classList], tagName, innerHTML }; + return { classList: [...classList], tagName, innerHTML: sanitize(innerHTML) }; }, lastCrumb() { const { children } = last(this.crumbs); @@ -30,7 +35,7 @@ export default { return { tagName, className, - text: this.$route.meta.nameGenerator(this.$store.state), + text: this.$route.meta.nameGenerator(), path: { to: this.$route.name }, }; }, @@ -43,14 +48,14 @@ export default { <li v-for="(crumb, index) in rootCrumbs" :key="index" - v-safe-html="crumb.innerHTML" :class="crumb.className" + v-html="crumb.innerHTML" ></li> <li v-if="!isRootRoute"> <router-link ref="rootRouteLink" :to="rootRoute.path"> - {{ rootRoute.meta.nameGenerator($store.state) }} + {{ rootRoute.meta.nameGenerator() }} </router-link> - <component :is="divider.tagName" v-safe-html="divider.innerHTML" :class="divider.classList" /> + <component :is="divider.tagName" :class="divider.classList" v-html="divider.innerHTML" /> </li> <li> <component :is="lastCrumb.tagName" ref="lastCrumb" :class="lastCrumb.className"> |