diff options
Diffstat (limited to 'app/assets/javascripts/registry')
23 files changed, 248 insertions, 1077 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 f46068acd68..80ed9a32039 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,6 +1,6 @@ <script> import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { sprintf, n__ } from '~/locale'; +import { sprintf, n__, s__ } from '~/locale'; import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; @@ -23,6 +23,8 @@ import { ROOT_IMAGE_TOOLTIP, } from '../../constants/index'; +import getContainerRepositoryTagsCountQuery from '../../graphql/queries/get_container_repository_tags_count.query.graphql'; + export default { name: 'DetailsHeader', components: { GlButton, GlIcon, TitleArea, MetadataItem }, @@ -35,60 +37,77 @@ export default { type: Object, required: true, }, - metadataLoading: { - type: Boolean, - required: false, - default: false, - }, disabled: { type: Boolean, default: false, required: false, }, }, + data() { + return { + containerRepository: {}, + fetchTagsCount: false, + }; + }, + apollo: { + containerRepository: { + query: getContainerRepositoryTagsCountQuery, + variables() { + return { + id: this.image.id, + }; + }, + }, + }, computed: { + imageDetails() { + return { ...this.image, ...this.containerRepository }; + }, visibilityIcon() { - return this.image?.project?.visibility === 'public' ? 'eye' : 'eye-slash'; + return this.imageDetails?.project?.visibility === 'public' ? 'eye' : 'eye-slash'; }, timeAgo() { - return this.timeFormatted(this.image.updatedAt); + return this.timeFormatted(this.imageDetails.updatedAt); }, updatedText() { return sprintf(UPDATED_AT, { time: this.timeAgo }); }, tagCountText() { - return n__('%d tag', '%d tags', this.image.tagsCount); + if (this.$apollo.queries.containerRepository.loading) { + return s__('ContainerRegistry|-- tags'); + } + return n__('%d tag', '%d tags', this.imageDetails.tagsCount); }, cleanupTextAndTooltip() { - if (!this.image.project.containerExpirationPolicy?.enabled) { + if (!this.imageDetails.project.containerExpirationPolicy?.enabled) { return { text: CLEANUP_DISABLED_TEXT, tooltip: CLEANUP_DISABLED_TOOLTIP }; } return { [UNSCHEDULED_STATUS]: { text: sprintf(CLEANUP_UNSCHEDULED_TEXT, { - time: this.timeFormatted(this.image.project.containerExpirationPolicy.nextRunAt), + time: this.timeFormatted(this.imageDetails.project.containerExpirationPolicy.nextRunAt), }), }, [SCHEDULED_STATUS]: { text: CLEANUP_SCHEDULED_TEXT, tooltip: CLEANUP_SCHEDULED_TOOLTIP }, [ONGOING_STATUS]: { text: CLEANUP_ONGOING_TEXT, tooltip: CLEANUP_ONGOING_TOOLTIP }, [UNFINISHED_STATUS]: { text: CLEANUP_UNFINISHED_TEXT, tooltip: CLEANUP_UNFINISHED_TOOLTIP }, - }[this.image?.expirationPolicyCleanupStatus]; + }[this.imageDetails?.expirationPolicyCleanupStatus]; }, deleteButtonDisabled() { - return this.disabled || !this.image.canDelete; + return this.disabled || !this.imageDetails.canDelete; }, rootImageTooltip() { - return !this.image.name ? ROOT_IMAGE_TOOLTIP : ''; + return !this.imageDetails.name ? ROOT_IMAGE_TOOLTIP : ''; }, imageName() { - return this.image.name || ROOT_IMAGE_TEXT; + return this.imageDetails.name || ROOT_IMAGE_TEXT; }, }, }; </script> <template> - <title-area :metadata-loading="metadataLoading"> + <title-area> <template #title> <span data-testid="title"> {{ imageName }} @@ -124,13 +143,8 @@ export default { /> </template> <template #right-actions> - <gl-button - v-if="!metadataLoading" - variant="danger" - :disabled="deleteButtonDisabled" - @click="$emit('delete')" - > - {{ __('Delete') }} + <gl-button variant="danger" :disabled="deleteButtonDisabled" @click="$emit('delete')"> + {{ __('Delete image repository') }} </gl-button> </template> </title-area> 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 bc10246614a..3e19a646f53 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 @@ -1,19 +1,32 @@ <script> -import { GlButton } from '@gitlab/ui'; -import { REMOVE_TAGS_BUTTON_TITLE, TAGS_LIST_TITLE } from '../../constants/index'; +import { GlButton, GlKeysetPagination } from '@gitlab/ui'; +import createFlash from '~/flash'; +import { joinPaths } from '~/lib/utils/url_utility'; +import { + REMOVE_TAGS_BUTTON_TITLE, + TAGS_LIST_TITLE, + GRAPHQL_PAGE_SIZE, + FETCH_IMAGES_LIST_ERROR_MESSAGE, +} from '../../constants/index'; +import getContainerRepositoryTagsQuery from '../../graphql/queries/get_container_repository_tags.query.graphql'; +import EmptyState from './empty_state.vue'; import TagsListRow from './tags_list_row.vue'; +import TagsLoader from './tags_loader.vue'; export default { name: 'TagsList', components: { GlButton, + GlKeysetPagination, TagsListRow, + EmptyState, + TagsLoader, }, + inject: ['config'], props: { - tags: { - type: Array, - required: false, - default: () => [], + id: { + type: [Number, String], + required: true, }, isMobile: { type: Boolean, @@ -25,17 +38,46 @@ export default { default: false, required: false, }, + isImageLoading: { + type: Boolean, + default: false, + required: false, + }, }, i18n: { REMOVE_TAGS_BUTTON_TITLE, TAGS_LIST_TITLE, }, + apollo: { + containerRepository: { + query: getContainerRepositoryTagsQuery, + variables() { + return this.queryVariables; + }, + error() { + createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE }); + }, + }, + }, data() { return { selectedItems: {}, + containerRepository: {}, }; }, computed: { + tags() { + return this.containerRepository?.tags?.nodes || []; + }, + tagsPageInfo() { + return this.containerRepository?.tags?.pageInfo; + }, + queryVariables() { + return { + id: joinPaths(this.config.gidPrefix, `${this.id}`), + first: GRAPHQL_PAGE_SIZE, + }; + }, hasSelectedItems() { return this.tags.some((tag) => this.selectedItems[tag.name]); }, @@ -45,42 +87,93 @@ export default { multiDeleteButtonIsDisabled() { return !this.hasSelectedItems || this.disabled; }, + showPagination() { + return this.tagsPageInfo.hasPreviousPage || this.tagsPageInfo.hasNextPage; + }, + hasNoTags() { + return this.tags.length === 0; + }, + isLoading() { + return this.isImageLoading || this.$apollo.queries.containerRepository.loading; + }, }, methods: { updateSelectedItems(name) { this.$set(this.selectedItems, name, !this.selectedItems[name]); }, + mapTagsToBeDleeted(items) { + return this.tags.filter((tag) => items[tag.name]); + }, + fetchNextPage() { + this.$apollo.queries.containerRepository.fetchMore({ + variables: { + after: this.tagsPageInfo?.endCursor, + first: GRAPHQL_PAGE_SIZE, + }, + updateQuery(previousResult, { fetchMoreResult }) { + return fetchMoreResult; + }, + }); + }, + fetchPreviousPage() { + this.$apollo.queries.containerRepository.fetchMore({ + variables: { + first: null, + before: this.tagsPageInfo?.startCursor, + last: GRAPHQL_PAGE_SIZE, + }, + updateQuery(previousResult, { fetchMoreResult }) { + return fetchMoreResult; + }, + }); + }, }, }; </script> <template> <div> - <div class="gl-display-flex gl-justify-content-space-between gl-mb-3"> - <h5 data-testid="list-title"> - {{ $options.i18n.TAGS_LIST_TITLE }} - </h5> + <tags-loader v-if="isLoading" /> + <template v-else> + <empty-state v-if="hasNoTags" :no-containers-image="config.noContainersImage" /> + <template v-else> + <div class="gl-display-flex gl-justify-content-space-between gl-mb-3"> + <h5 data-testid="list-title"> + {{ $options.i18n.TAGS_LIST_TITLE }} + </h5> - <gl-button - v-if="showMultiDeleteButton" - :disabled="multiDeleteButtonIsDisabled" - category="secondary" - variant="danger" - @click="$emit('delete', selectedItems)" - > - {{ $options.i18n.REMOVE_TAGS_BUTTON_TITLE }} - </gl-button> - </div> - <tags-list-row - v-for="(tag, index) in tags" - :key="tag.path" - :tag="tag" - :first="index === 0" - :selected="selectedItems[tag.name]" - :is-mobile="isMobile" - :disabled="disabled" - @select="updateSelectedItems(tag.name)" - @delete="$emit('delete', { [tag.name]: true })" - /> + <gl-button + v-if="showMultiDeleteButton" + :disabled="multiDeleteButtonIsDisabled" + category="secondary" + variant="danger" + @click="$emit('delete', mapTagsToBeDleeted(selectedItems))" + > + {{ $options.i18n.REMOVE_TAGS_BUTTON_TITLE }} + </gl-button> + </div> + <tags-list-row + v-for="(tag, index) in tags" + :key="tag.path" + :tag="tag" + :first="index === 0" + :selected="selectedItems[tag.name]" + :is-mobile="isMobile" + :disabled="disabled" + @select="updateSelectedItems(tag.name)" + @delete="$emit('delete', mapTagsToBeDleeted({ [tag.name]: true }))" + /> + <div class="gl-display-flex gl-justify-content-center"> + <gl-keyset-pagination + v-if="showPagination" + :has-next-page="tagsPageInfo.hasNextPage" + :has-previous-page="tagsPageInfo.hasPreviousPage" + class="gl-mt-3" + @prev="fetchPreviousPage" + @next="fetchNextPage" + /> + </div> + </template> + </template> </div> </template> 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 74027a376a7..45eb2ce51e4 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 @@ -50,6 +50,11 @@ export default { default: false, required: false, }, + disabled: { + type: Boolean, + default: false, + required: false, + }, }, i18n: { REMOVE_TAG_BUTTON_TITLE, @@ -92,19 +97,25 @@ export default { tagLocation() { return this.tag.path?.replace(`:${this.tag.name}`, ''); }, - invalidTag() { + isInvalidTag() { return !this.tag.digest; }, + isCheckboxDisabled() { + return this.isInvalidTag || this.disabled; + }, + isDeleteDisabled() { + return this.isInvalidTag || this.disabled || !this.tag.canDelete; + }, }, }; </script> <template> - <list-item v-bind="$attrs" :selected="selected"> + <list-item v-bind="$attrs" :selected="selected" :disabled="disabled"> <template #left-action> <gl-form-checkbox v-if="tag.canDelete" - :disabled="invalidTag" + :disabled="isCheckboxDisabled" class="gl-m-0" :checked="selected" @change="$emit('select')" @@ -126,10 +137,11 @@ export default { :title="tag.location" :text="tag.location" category="tertiary" + :disabled="disabled" /> <gl-icon - v-if="invalidTag" + v-if="isInvalidTag" v-gl-tooltip="{ title: $options.i18n.MISSING_MANIFEST_WARNING_TOOLTIP }" name="warning" class="gl-text-orange-500 gl-mb-2 gl-ml-2" @@ -162,7 +174,7 @@ export default { </template> <template #right-action> <delete-button - :disabled="!tag.canDelete || invalidTag" + :disabled="isDeleteDisabled" :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE" :tooltip-title="$options.i18n.REMOVE_TAG_BUTTON_DISABLE_TOOLTIP" :tooltip-disabled="tag.canDelete" @@ -172,7 +184,7 @@ export default { /> </template> - <template v-if="!invalidTag" #details-published> + <template v-if="!isInvalidTag" #details-published> <details-row icon="clock" data-testid="published-date-detail"> <gl-sprintf :message="$options.i18n.PUBLISHED_DETAILS_ROW_TEXT"> <template #repositoryPath> @@ -187,7 +199,7 @@ export default { </gl-sprintf> </details-row> </template> - <template v-if="!invalidTag" #details-manifest-digest> + <template v-if="!isInvalidTag" #details-manifest-digest> <details-row icon="log" data-testid="manifest-detail"> <gl-sprintf :message="$options.i18n.MANIFEST_DETAILS_ROW_TEST"> <template #digest> @@ -200,10 +212,11 @@ export default { :text="tag.digest" category="tertiary" size="small" + :disabled="disabled" /> </details-row> </template> - <template v-if="!invalidTag" #details-configuration-digest> + <template v-if="!isInvalidTag" #details-configuration-digest> <details-row icon="cloud-gear" data-testid="configuration-detail"> <gl-sprintf :message="$options.i18n.CONFIGURATION_DETAILS_ROW_TEST"> <template #digest> @@ -216,6 +229,7 @@ export default { :text="formattedRevision" category="tertiary" size="small" + :disabled="disabled" /> </details-row> </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 0373a84b271..930ad01c758 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 @@ -78,6 +78,9 @@ export default { imageName() { return this.item.name ? this.item.path : `${this.item.path}/ ${ROOT_IMAGE_TEXT}`; }, + routerLinkEvent() { + return this.deleting ? '' : 'click'; + }, }, }; </script> @@ -97,6 +100,7 @@ export default { class="gl-text-body gl-font-weight-bold" data-testid="details-link" data-qa-selector="registry_image_content" + :event="routerLinkEvent" :to="{ name: 'details', params: { id } }" > {{ imageName }} diff --git a/app/assets/javascripts/registry/explorer/constants/details.js b/app/assets/javascripts/registry/explorer/constants/details.js index 7220f9646db..5dcc042a9c4 100644 --- a/app/assets/javascripts/registry/explorer/constants/details.js +++ b/app/assets/javascripts/registry/explorer/constants/details.js @@ -31,7 +31,7 @@ export const CONFIGURATION_DETAILS_ROW_TEST = s__( ); export const REMOVE_TAG_BUTTON_TITLE = s__('ContainerRegistry|Remove tag'); -export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Delete selected'); +export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Delete selected tags'); export const REMOVE_TAG_CONFIRMATION_TEXT = s__( `ContainerRegistry|You are about to remove %{item}. Are you sure?`, diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql index 3fd019467ac..88c2e667afd 100644 --- a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql +++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.query.graphql @@ -1,12 +1,4 @@ -#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" - -query getContainerRepositoryDetails( - $id: ID! - $first: Int - $last: Int - $after: String - $before: String -) { +query getContainerRepositoryDetails($id: ID!) { containerRepository(id: $id) { id name @@ -16,25 +8,8 @@ query getContainerRepositoryDetails( canDelete createdAt updatedAt - tagsCount expirationPolicyStartedAt expirationPolicyCleanupStatus - tags(after: $after, before: $before, first: $first, last: $last) { - nodes { - digest - location - path - name - revision - shortRevision - createdAt - totalSize - canDelete - } - pageInfo { - ...PageInfo - } - } project { visibility containerExpirationPolicy { diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql new file mode 100644 index 00000000000..a703c2dd0ac --- /dev/null +++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags.query.graphql @@ -0,0 +1,29 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" + +query getContainerRepositoryTags( + $id: ID! + $first: Int + $last: Int + $after: String + $before: String +) { + containerRepository(id: $id) { + id + tags(after: $after, before: $before, first: $first, last: $last) { + nodes { + digest + location + path + name + revision + shortRevision + createdAt + totalSize + canDelete + } + pageInfo { + ...PageInfo + } + } + } +} diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql new file mode 100644 index 00000000000..9092a71edb0 --- /dev/null +++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql @@ -0,0 +1,6 @@ +query getContainerRepositoryTagsCount($id: ID!) { + containerRepository(id: $id) { + id + tagsCount + } +} diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue index 2f515356fa7..34ec3b085a5 100644 --- a/app/assets/javascripts/registry/explorer/pages/details.vue +++ b/app/assets/javascripts/registry/explorer/pages/details.vue @@ -1,5 +1,5 @@ <script> -import { GlKeysetPagination, GlResizeObserverDirective } from '@gitlab/ui'; +import { GlResizeObserverDirective } from '@gitlab/ui'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; @@ -21,7 +21,6 @@ import { ALERT_SUCCESS_TAGS, ALERT_DANGER_TAGS, ALERT_DANGER_IMAGE, - GRAPHQL_PAGE_SIZE, FETCH_IMAGES_LIST_ERROR_MESSAGE, UNFINISHED_STATUS, MISSING_OR_DELETED_IMAGE_BREADCRUMB, @@ -36,7 +35,6 @@ export default { DeleteAlert, PartialCleanupAlert, DetailsHeader, - GlKeysetPagination, DeleteModal, TagsList, TagsLoader, @@ -50,16 +48,12 @@ export default { mixins: [Tracking.mixin()], inject: ['breadCrumbState', 'config'], apollo: { - image: { + containerRepository: { query: getContainerRepositoryDetailsQuery, variables() { return this.queryVariables; }, - update(data) { - return data.containerRepository; - }, - result({ data }) { - this.tagsPageInfo = data.containerRepository?.tags?.pageInfo; + result() { this.updateBreadcrumb(); }, error() { @@ -69,8 +63,7 @@ export default { }, data() { return { - image: {}, - tagsPageInfo: {}, + containerRepository: {}, itemsToBeDeleted: [], isMobile: false, mutationLoading: false, @@ -83,19 +76,15 @@ export default { queryVariables() { return { id: joinPaths(this.config.gidPrefix, `${this.$route.params.id}`), - first: GRAPHQL_PAGE_SIZE, }; }, isLoading() { - return this.$apollo.queries.image.loading || this.mutationLoading; - }, - tags() { - return this.image?.tags?.nodes || []; + return this.$apollo.queries.containerRepository.loading || this.mutationLoading; }, showPartialCleanupWarning() { return ( this.config.showUnfinishedTagCleanupCallout && - this.image?.expirationPolicyCleanupStatus === UNFINISHED_STATUS && + this.containerRepository?.expirationPolicyCleanupStatus === UNFINISHED_STATUS && !this.hidePartialCleanupWarning ); }, @@ -105,26 +94,20 @@ export default { this.itemsToBeDeleted?.length > 1 ? 'bulk_registry_tag_delete' : 'registry_tag_delete', }; }, - showPagination() { - return this.tagsPageInfo.hasPreviousPage || this.tagsPageInfo.hasNextPage; - }, - hasNoTags() { - return this.tags.length === 0; - }, pageActionsAreDisabled() { - return Boolean(this.image?.status); + return Boolean(this.containerRepository?.status); }, }, methods: { updateBreadcrumb() { - const name = this.image?.id - ? this.image?.name || ROOT_IMAGE_TEXT + const name = this.containerRepository?.id + ? this.containerRepository?.name || ROOT_IMAGE_TEXT : MISSING_OR_DELETED_IMAGE_BREADCRUMB; this.breadCrumbState.updateName(name); }, deleteTags(toBeDeleted) { this.deleteImageAlert = false; - this.itemsToBeDeleted = this.tags.filter((tag) => toBeDeleted[tag.name]); + this.itemsToBeDeleted = toBeDeleted; this.track('click_button'); this.$refs.deleteModal.show(); }, @@ -170,33 +153,6 @@ export default { handleResize() { this.isMobile = GlBreakpointInstance.getBreakpointSize() === 'xs'; }, - fetchNextPage() { - if (this.tagsPageInfo?.hasNextPage) { - this.$apollo.queries.image.fetchMore({ - variables: { - after: this.tagsPageInfo?.endCursor, - first: GRAPHQL_PAGE_SIZE, - }, - updateQuery(previousResult, { fetchMoreResult }) { - return fetchMoreResult; - }, - }); - } - }, - fetchPreviousPage() { - if (this.tagsPageInfo?.hasPreviousPage) { - this.$apollo.queries.image.fetchMore({ - variables: { - first: null, - before: this.tagsPageInfo?.startCursor, - last: GRAPHQL_PAGE_SIZE, - }, - updateQuery(previousResult, { fetchMoreResult }) { - return fetchMoreResult; - }, - }); - } - }, dismissPartialCleanupWarning() { this.hidePartialCleanupWarning = true; axios.post(this.config.userCalloutsPath, { @@ -205,7 +161,7 @@ export default { }, deleteImage() { this.deleteImageAlert = true; - this.itemsToBeDeleted = [{ path: this.image.path }]; + this.itemsToBeDeleted = [{ path: this.containerRepository.path }]; this.$refs.deleteModal.show(); }, deleteImageError() { @@ -221,7 +177,7 @@ export default { <template> <div v-gl-resize-observer="handleResize" class="gl-my-3"> - <template v-if="image"> + <template v-if="containerRepository"> <delete-alert v-model="deleteAlertType" :garbage-collection-help-page-path="config.garbageCollectionHelpPagePath" @@ -236,40 +192,27 @@ export default { @dismiss="dismissPartialCleanupWarning" /> - <status-alert v-if="image.status" :status="image.status" /> + <status-alert v-if="containerRepository.status" :status="containerRepository.status" /> <details-header - :image="image" - :metadata-loading="isLoading" + v-if="!isLoading" + :image="containerRepository" :disabled="pageActionsAreDisabled" @delete="deleteImage" /> <tags-loader v-if="isLoading" /> - <template v-else> - <empty-state v-if="hasNoTags" :no-containers-image="config.noContainersImage" /> - <template v-else> - <tags-list - :tags="tags" - :is-mobile="isMobile" - :disabled="pageActionsAreDisabled" - @delete="deleteTags" - /> - <div class="gl-display-flex gl-justify-content-center"> - <gl-keyset-pagination - v-if="showPagination" - :has-next-page="tagsPageInfo.hasNextPage" - :has-previous-page="tagsPageInfo.hasPreviousPage" - class="gl-mt-3" - @prev="fetchPreviousPage" - @next="fetchNextPage" - /> - </div> - </template> - </template> + <tags-list + v-else + :id="$route.params.id" + :is-image-loading="isLoading" + :is-mobile="isMobile" + :disabled="pageActionsAreDisabled" + @delete="deleteTags" + /> <delete-image - :id="image.id" + :id="containerRepository.id" ref="deleteImage" use-update-fn @start="deleteImageIniit" diff --git a/app/assets/javascripts/registry/settings/components/expiration_dropdown.vue b/app/assets/javascripts/registry/settings/components/expiration_dropdown.vue deleted file mode 100644 index d75fb31fd98..00000000000 --- a/app/assets/javascripts/registry/settings/components/expiration_dropdown.vue +++ /dev/null @@ -1,50 +0,0 @@ -<script> -import { GlFormGroup, GlFormSelect } from '@gitlab/ui'; - -export default { - components: { - GlFormGroup, - GlFormSelect, - }, - props: { - formOptions: { - type: Array, - required: false, - default: () => [], - }, - disabled: { - type: Boolean, - required: false, - default: false, - }, - value: { - type: String, - required: false, - default: '', - }, - name: { - type: String, - required: true, - }, - label: { - type: String, - required: true, - }, - }, -}; -</script> - -<template> - <gl-form-group :id="`${name}-form-group`" :label-for="name" :label="label"> - <gl-form-select :id="name" :value="value" :disabled="disabled" @input="$emit('input', $event)"> - <option - v-for="option in formOptions" - :key="option.key" - :value="option.key" - data-testid="option" - > - {{ option.label }} - </option> - </gl-form-select> - </gl-form-group> -</template> diff --git a/app/assets/javascripts/registry/settings/components/expiration_input.vue b/app/assets/javascripts/registry/settings/components/expiration_input.vue deleted file mode 100644 index 42b7c7918a5..00000000000 --- a/app/assets/javascripts/registry/settings/components/expiration_input.vue +++ /dev/null @@ -1,110 +0,0 @@ -<script> -import { GlFormGroup, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui'; -import { NAME_REGEX_LENGTH, TEXT_AREA_INVALID_FEEDBACK } from '../constants'; - -export default { - components: { - GlFormGroup, - GlFormInput, - GlSprintf, - GlLink, - }, - inject: ['tagsRegexHelpPagePath'], - props: { - error: { - type: String, - required: false, - default: '', - }, - disabled: { - type: Boolean, - required: false, - default: false, - }, - value: { - type: String, - required: false, - default: '', - }, - name: { - type: String, - required: true, - }, - label: { - type: String, - required: true, - }, - placeholder: { - type: String, - required: false, - default: '', - }, - description: { - type: String, - required: true, - }, - }, - computed: { - textAreaLengthErrorMessage() { - return this.isInputValid(this.value) ? '' : TEXT_AREA_INVALID_FEEDBACK; - }, - inputValidation() { - const nameRegexErrors = this.error || this.textAreaLengthErrorMessage; - return { - state: nameRegexErrors === null ? null : !nameRegexErrors, - message: nameRegexErrors, - }; - }, - internalValue: { - get() { - return this.value; - }, - set(value) { - this.$emit('input', value); - this.$emit('validation', this.isInputValid(value)); - }, - }, - }, - methods: { - isInputValid(value) { - return !value || value.length <= NAME_REGEX_LENGTH; - }, - }, -}; -</script> - -<template> - <gl-form-group - :id="`${name}-form-group`" - :label-for="name" - :state="inputValidation.state" - :invalid-feedback="inputValidation.message" - > - <template #label> - <span data-testid="label"> - <gl-sprintf :message="label"> - <template #italic="{ content }"> - <i>{{ content }}</i> - </template> - </gl-sprintf> - </span> - </template> - <gl-form-input - :id="name" - v-model="internalValue" - :placeholder="placeholder" - :state="inputValidation.state" - :disabled="disabled" - trim - /> - <template #description> - <span data-testid="description" class="gl-text-gray-400"> - <gl-sprintf :message="description"> - <template #link="{ content }"> - <gl-link :href="tagsRegexHelpPagePath" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </span> - </template> - </gl-form-group> -</template> diff --git a/app/assets/javascripts/registry/settings/components/expiration_run_text.vue b/app/assets/javascripts/registry/settings/components/expiration_run_text.vue deleted file mode 100644 index fd9ca6a54c5..00000000000 --- a/app/assets/javascripts/registry/settings/components/expiration_run_text.vue +++ /dev/null @@ -1,46 +0,0 @@ -<script> -import { GlFormGroup, GlFormInput } from '@gitlab/ui'; -import { NEXT_CLEANUP_LABEL, NOT_SCHEDULED_POLICY_TEXT } from '~/registry/settings/constants'; - -export default { - components: { - GlFormGroup, - GlFormInput, - }, - props: { - value: { - type: String, - required: false, - default: NOT_SCHEDULED_POLICY_TEXT, - }, - enabled: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - parsedValue() { - return this.enabled ? this.value : NOT_SCHEDULED_POLICY_TEXT; - }, - }, - i18n: { - NEXT_CLEANUP_LABEL, - }, -}; -</script> - -<template> - <gl-form-group - id="expiration-policy-info-text-group" - :label="$options.i18n.NEXT_CLEANUP_LABEL" - label-for="expiration-policy-info-text" - > - <gl-form-input - id="expiration-policy-info-text" - class="gl-pl-0!" - plaintext - :value="parsedValue" - /> - </gl-form-group> -</template> diff --git a/app/assets/javascripts/registry/settings/components/expiration_toggle.vue b/app/assets/javascripts/registry/settings/components/expiration_toggle.vue deleted file mode 100644 index 6aa78c69ba9..00000000000 --- a/app/assets/javascripts/registry/settings/components/expiration_toggle.vue +++ /dev/null @@ -1,62 +0,0 @@ -<script> -import { GlFormGroup, GlToggle, GlSprintf } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import { ENABLED_TOGGLE_DESCRIPTION, DISABLED_TOGGLE_DESCRIPTION } from '../constants'; - -export default { - i18n: { - toggleLabel: s__('ContainerRegistry|Enable expiration policy'), - }, - components: { - GlFormGroup, - GlToggle, - GlSprintf, - }, - props: { - disabled: { - type: Boolean, - required: false, - default: false, - }, - value: { - type: Boolean, - required: false, - default: false, - }, - }, - computed: { - enabled: { - get() { - return this.value; - }, - set(value) { - this.$emit('input', value); - }, - }, - toggleText() { - return this.enabled ? ENABLED_TOGGLE_DESCRIPTION : DISABLED_TOGGLE_DESCRIPTION; - }, - }, -}; -</script> - -<template> - <gl-form-group id="expiration-policy-toggle-group" label-for="expiration-policy-toggle"> - <div class="gl-display-flex"> - <gl-toggle - id="expiration-policy-toggle" - v-model="enabled" - :label="$options.i18n.toggleLabel" - label-position="hidden" - :disabled="disabled" - /> - <span class="gl-ml-5 gl-line-height-24" data-testid="description"> - <gl-sprintf :message="toggleText"> - <template #strong="{ content }"> - <strong>{{ content }}</strong> - </template> - </gl-sprintf> - </span> - </div> - </gl-form-group> -</template> diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue deleted file mode 100644 index 480590ec71e..00000000000 --- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue +++ /dev/null @@ -1,106 +0,0 @@ -<script> -import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; -import { isEqual, get, isEmpty } from 'lodash'; -import { - FETCH_SETTINGS_ERROR_MESSAGE, - UNAVAILABLE_FEATURE_TITLE, - UNAVAILABLE_FEATURE_INTRO_TEXT, - UNAVAILABLE_USER_FEATURE_TEXT, - UNAVAILABLE_ADMIN_FEATURE_TEXT, -} from '../constants'; -import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.query.graphql'; - -import SettingsForm from './settings_form.vue'; - -export default { - components: { - SettingsForm, - GlAlert, - GlSprintf, - GlLink, - }, - inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries'], - i18n: { - UNAVAILABLE_FEATURE_TITLE, - UNAVAILABLE_FEATURE_INTRO_TEXT, - FETCH_SETTINGS_ERROR_MESSAGE, - }, - apollo: { - containerExpirationPolicy: { - query: expirationPolicyQuery, - variables() { - return { - projectPath: this.projectPath, - }; - }, - update: (data) => data.project?.containerExpirationPolicy, - result({ data }) { - this.workingCopy = { ...get(data, 'project.containerExpirationPolicy', {}) }; - }, - error(e) { - this.fetchSettingsError = e; - }, - }, - }, - data() { - return { - fetchSettingsError: false, - containerExpirationPolicy: null, - workingCopy: {}, - }; - }, - computed: { - isDisabled() { - return !(this.containerExpirationPolicy || this.enableHistoricEntries); - }, - showDisabledFormMessage() { - return this.isDisabled && !this.fetchSettingsError; - }, - unavailableFeatureMessage() { - return this.isAdmin ? UNAVAILABLE_ADMIN_FEATURE_TEXT : UNAVAILABLE_USER_FEATURE_TEXT; - }, - isEdited() { - if (isEmpty(this.containerExpirationPolicy) && isEmpty(this.workingCopy)) { - return false; - } - return !isEqual(this.containerExpirationPolicy, this.workingCopy); - }, - }, - methods: { - restoreOriginal() { - this.workingCopy = { ...this.containerExpirationPolicy }; - }, - }, -}; -</script> - -<template> - <div> - <settings-form - v-if="!isDisabled" - v-model="workingCopy" - :is-loading="$apollo.queries.containerExpirationPolicy.loading" - :is-edited="isEdited" - @reset="restoreOriginal" - /> - <template v-else> - <gl-alert - v-if="showDisabledFormMessage" - :dismissible="false" - :title="$options.i18n.UNAVAILABLE_FEATURE_TITLE" - variant="tip" - > - {{ $options.i18n.UNAVAILABLE_FEATURE_INTRO_TEXT }} - - <gl-sprintf :message="unavailableFeatureMessage"> - <template #link="{ content }"> - <gl-link :href="adminSettingsPath" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </gl-alert> - <gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false"> - <gl-sprintf :message="$options.i18n.FETCH_SETTINGS_ERROR_MESSAGE" /> - </gl-alert> - </template> - </div> -</template> diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue deleted file mode 100644 index 1360e09a75d..00000000000 --- a/app/assets/javascripts/registry/settings/components/settings_form.vue +++ /dev/null @@ -1,313 +0,0 @@ -<script> -import { GlCard, GlButton, GlSprintf } from '@gitlab/ui'; -import { - UPDATE_SETTINGS_ERROR_MESSAGE, - UPDATE_SETTINGS_SUCCESS_MESSAGE, - SET_CLEANUP_POLICY_BUTTON, - KEEP_HEADER_TEXT, - KEEP_INFO_TEXT, - KEEP_N_LABEL, - NAME_REGEX_KEEP_LABEL, - NAME_REGEX_KEEP_DESCRIPTION, - REMOVE_HEADER_TEXT, - REMOVE_INFO_TEXT, - EXPIRATION_SCHEDULE_LABEL, - NAME_REGEX_LABEL, - NAME_REGEX_PLACEHOLDER, - NAME_REGEX_DESCRIPTION, - CADENCE_LABEL, - EXPIRATION_POLICY_FOOTER_NOTE, -} from '~/registry/settings/constants'; -import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql'; -import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update'; -import { formOptionsGenerator } from '~/registry/settings/utils'; -import Tracking from '~/tracking'; -import ExpirationDropdown from './expiration_dropdown.vue'; -import ExpirationInput from './expiration_input.vue'; -import ExpirationRunText from './expiration_run_text.vue'; -import ExpirationToggle from './expiration_toggle.vue'; - -export default { - components: { - GlCard, - GlButton, - GlSprintf, - ExpirationDropdown, - ExpirationInput, - ExpirationToggle, - ExpirationRunText, - }, - mixins: [Tracking.mixin()], - inject: ['projectPath'], - props: { - value: { - type: Object, - required: true, - }, - isLoading: { - type: Boolean, - required: false, - default: false, - }, - isEdited: { - type: Boolean, - required: false, - default: false, - }, - }, - - formOptions: formOptionsGenerator(), - i18n: { - KEEP_HEADER_TEXT, - KEEP_INFO_TEXT, - KEEP_N_LABEL, - NAME_REGEX_KEEP_LABEL, - SET_CLEANUP_POLICY_BUTTON, - NAME_REGEX_KEEP_DESCRIPTION, - REMOVE_HEADER_TEXT, - REMOVE_INFO_TEXT, - EXPIRATION_SCHEDULE_LABEL, - NAME_REGEX_LABEL, - NAME_REGEX_PLACEHOLDER, - NAME_REGEX_DESCRIPTION, - CADENCE_LABEL, - EXPIRATION_POLICY_FOOTER_NOTE, - }, - data() { - return { - tracking: { - label: 'docker_container_retention_and_expiration_policies', - }, - apiErrors: {}, - localErrors: {}, - mutationLoading: false, - }; - }, - computed: { - prefilledForm() { - return { - ...this.value, - cadence: this.findDefaultOption('cadence'), - keepN: this.findDefaultOption('keepN'), - olderThan: this.findDefaultOption('olderThan'), - }; - }, - showLoadingIcon() { - return this.isLoading || this.mutationLoading; - }, - fieldsAreValid() { - return Object.values(this.localErrors).every((error) => error); - }, - isSubmitButtonDisabled() { - return !this.fieldsAreValid || this.showLoadingIcon; - }, - isCancelButtonDisabled() { - return !this.isEdited || this.isLoading || this.mutationLoading; - }, - isFieldDisabled() { - return this.showLoadingIcon || !this.value.enabled; - }, - mutationVariables() { - return { - projectPath: this.projectPath, - enabled: this.prefilledForm.enabled, - cadence: this.prefilledForm.cadence, - olderThan: this.prefilledForm.olderThan, - keepN: this.prefilledForm.keepN, - nameRegex: this.prefilledForm.nameRegex, - nameRegexKeep: this.prefilledForm.nameRegexKeep, - }; - }, - }, - methods: { - findDefaultOption(option) { - return this.value[option] || this.$options.formOptions[option].find((f) => f.default)?.key; - }, - reset() { - this.track('reset_form'); - this.apiErrors = {}; - this.localErrors = {}; - this.$emit('reset'); - }, - setApiErrors(response) { - this.apiErrors = response.graphQLErrors.reduce((acc, curr) => { - curr.extensions.problems.forEach((item) => { - acc[item.path[0]] = item.message; - }); - return acc; - }, {}); - }, - setLocalErrors(state, model) { - this.localErrors = { - ...this.localErrors, - [model]: state, - }; - }, - submit() { - this.track('submit_form'); - this.apiErrors = {}; - this.mutationLoading = true; - return this.$apollo - .mutate({ - mutation: updateContainerExpirationPolicyMutation, - variables: { - input: this.mutationVariables, - }, - update: updateContainerExpirationPolicy(this.projectPath), - }) - .then(({ data }) => { - const errorMessage = data?.updateContainerExpirationPolicy?.errors[0]; - if (errorMessage) { - this.$toast.show(errorMessage, { type: 'error' }); - } else { - this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE, { type: 'success' }); - } - }) - .catch((error) => { - this.setApiErrors(error); - this.$toast.show(UPDATE_SETTINGS_ERROR_MESSAGE, { type: 'error' }); - }) - .finally(() => { - this.mutationLoading = false; - }); - }, - onModelChange(newValue, model) { - this.$emit('input', { ...this.value, [model]: newValue }); - this.apiErrors[model] = undefined; - }, - }, -}; -</script> - -<template> - <form ref="form-element" @submit.prevent="submit" @reset.prevent="reset"> - <expiration-toggle - :value="prefilledForm.enabled" - :disabled="showLoadingIcon" - class="gl-mb-0!" - data-testid="enable-toggle" - @input="onModelChange($event, 'enabled')" - /> - - <div class="gl-display-flex gl-mt-7"> - <expiration-dropdown - v-model="prefilledForm.cadence" - :disabled="isFieldDisabled" - :form-options="$options.formOptions.cadence" - :label="$options.i18n.CADENCE_LABEL" - name="cadence" - class="gl-mr-7 gl-mb-0!" - data-testid="cadence-dropdown" - @input="onModelChange($event, 'cadence')" - /> - <expiration-run-text - :value="prefilledForm.nextRunAt" - :enabled="prefilledForm.enabled" - class="gl-mb-0!" - /> - </div> - <gl-card class="gl-mt-7"> - <template #header> - {{ $options.i18n.KEEP_HEADER_TEXT }} - </template> - <template #default> - <div> - <p> - <gl-sprintf :message="$options.i18n.KEEP_INFO_TEXT"> - <template #strong="{ content }"> - <strong>{{ content }}</strong> - </template> - <template #secondStrong="{ content }"> - <strong>{{ content }}</strong> - </template> - </gl-sprintf> - </p> - <expiration-dropdown - v-model="prefilledForm.keepN" - :disabled="isFieldDisabled" - :form-options="$options.formOptions.keepN" - :label="$options.i18n.KEEP_N_LABEL" - name="keep-n" - data-testid="keep-n-dropdown" - @input="onModelChange($event, 'keepN')" - /> - <expiration-input - v-model="prefilledForm.nameRegexKeep" - :error="apiErrors.nameRegexKeep" - :disabled="isFieldDisabled" - :label="$options.i18n.NAME_REGEX_KEEP_LABEL" - :description="$options.i18n.NAME_REGEX_KEEP_DESCRIPTION" - name="keep-regex" - data-testid="keep-regex-input" - @input="onModelChange($event, 'nameRegexKeep')" - @validation="setLocalErrors($event, 'nameRegexKeep')" - /> - </div> - </template> - </gl-card> - <gl-card class="gl-mt-7"> - <template #header> - {{ $options.i18n.REMOVE_HEADER_TEXT }} - </template> - <template #default> - <div> - <p> - <gl-sprintf :message="$options.i18n.REMOVE_INFO_TEXT"> - <template #strong="{ content }"> - <strong>{{ content }}</strong> - </template> - <template #secondStrong="{ content }"> - <strong>{{ content }}</strong> - </template> - </gl-sprintf> - </p> - <expiration-dropdown - v-model="prefilledForm.olderThan" - :disabled="isFieldDisabled" - :form-options="$options.formOptions.olderThan" - :label="$options.i18n.EXPIRATION_SCHEDULE_LABEL" - name="older-than" - data-testid="older-than-dropdown" - @input="onModelChange($event, 'olderThan')" - /> - <expiration-input - v-model="prefilledForm.nameRegex" - :error="apiErrors.nameRegex" - :disabled="isFieldDisabled" - :label="$options.i18n.NAME_REGEX_LABEL" - :placeholder="$options.i18n.NAME_REGEX_PLACEHOLDER" - :description="$options.i18n.NAME_REGEX_DESCRIPTION" - name="remove-regex" - data-testid="remove-regex-input" - @input="onModelChange($event, 'nameRegex')" - @validation="setLocalErrors($event, 'nameRegex')" - /> - </div> - </template> - </gl-card> - <div class="gl-mt-7 gl-display-flex gl-align-items-center"> - <gl-button - data-testid="save-button" - type="submit" - :disabled="isSubmitButtonDisabled" - :loading="showLoadingIcon" - category="primary" - variant="confirm" - class="js-no-auto-disable gl-mr-4" - > - {{ $options.i18n.SET_CLEANUP_POLICY_BUTTON }} - </gl-button> - <gl-button - data-testid="cancel-button" - type="reset" - :disabled="isCancelButtonDisabled" - class="gl-mr-4" - > - {{ __('Cancel') }} - </gl-button> - <span class="gl-font-style-italic gl-text-gray-400">{{ - $options.i18n.EXPIRATION_POLICY_FOOTER_NOTE - }}</span> - </div> - </form> -</template> diff --git a/app/assets/javascripts/registry/settings/constants.js b/app/assets/javascripts/registry/settings/constants.js deleted file mode 100644 index 165c4aae3cb..00000000000 --- a/app/assets/javascripts/registry/settings/constants.js +++ /dev/null @@ -1,91 +0,0 @@ -import { s__, __ } from '~/locale'; - -export const SET_CLEANUP_POLICY_BUTTON = __('Save'); -export const UNAVAILABLE_FEATURE_TITLE = s__( - `ContainerRegistry|Cleanup policy for tags is disabled`, -); -export const UNAVAILABLE_FEATURE_INTRO_TEXT = s__( - `ContainerRegistry|This project's cleanup policy for tags is not enabled.`, -); -export const UNAVAILABLE_USER_FEATURE_TEXT = __(`Please contact your administrator.`); -export const UNAVAILABLE_ADMIN_FEATURE_TEXT = s__( - `ContainerRegistry| Please visit the %{linkStart}administration settings%{linkEnd} to enable this feature.`, -); - -export const TEXT_AREA_INVALID_FEEDBACK = s__( - 'ContainerRegistry|The value of this input should be less than 256 characters', -); - -export const KEEP_HEADER_TEXT = s__('ContainerRegistry|Keep these tags'); -export const KEEP_INFO_TEXT = s__( - 'ContainerRegistry|Tags that match these rules are %{strongStart}kept%{strongEnd}, even if they match a removal rule below. The %{secondStrongStart}latest%{secondStrongEnd} tag is always kept.', -); -export const KEEP_N_LABEL = s__('ContainerRegistry|Keep the most recent:'); -export const NAME_REGEX_KEEP_LABEL = s__('ContainerRegistry|Keep tags matching:'); -export const NAME_REGEX_KEEP_DESCRIPTION = s__( - 'ContainerRegistry|Tags with names that match this regex pattern are kept. %{linkStart}View regex examples.%{linkEnd}', -); - -export const REMOVE_HEADER_TEXT = s__('ContainerRegistry|Remove these tags'); -export const REMOVE_INFO_TEXT = s__( - 'ContainerRegistry|Tags that match these rules are %{strongStart}removed%{strongEnd}, unless a rule above says to keep them.', -); -export const EXPIRATION_SCHEDULE_LABEL = s__('ContainerRegistry|Remove tags older than:'); -export const NAME_REGEX_LABEL = s__('ContainerRegistry|Remove tags matching:'); -export const NAME_REGEX_PLACEHOLDER = '.*'; -export const NAME_REGEX_DESCRIPTION = s__( - 'ContainerRegistry|Tags with names that match this regex pattern are removed. %{linkStart}View regex examples.%{linkEnd}', -); - -export const ENABLED_TOGGLE_DESCRIPTION = s__( - 'ContainerRegistry|%{strongStart}Enabled%{strongEnd} - Tags that match the rules on this page are automatically scheduled for deletion.', -); -export const DISABLED_TOGGLE_DESCRIPTION = s__( - 'ContainerRegistry|%{strongStart}Disabled%{strongEnd} - Tags will not be automatically deleted.', -); - -export const CADENCE_LABEL = s__('ContainerRegistry|Run cleanup:'); - -export const NEXT_CLEANUP_LABEL = s__('ContainerRegistry|Next cleanup scheduled to run on:'); -export const NOT_SCHEDULED_POLICY_TEXT = s__('ContainerRegistry|Not yet scheduled'); -export const EXPIRATION_POLICY_FOOTER_NOTE = s__( - 'ContainerRegistry|Note: Any policy update will result in a change to the scheduled run date and time', -); - -export const KEEP_N_OPTIONS = [ - { key: 'ONE_TAG', variable: 1, default: false }, - { key: 'FIVE_TAGS', variable: 5, default: false }, - { key: 'TEN_TAGS', variable: 10, default: true }, - { key: 'TWENTY_FIVE_TAGS', variable: 25, default: false }, - { key: 'FIFTY_TAGS', variable: 50, default: false }, - { key: 'ONE_HUNDRED_TAGS', variable: 100, default: false }, -]; - -export const CADENCE_OPTIONS = [ - { key: 'EVERY_DAY', label: __('Every day'), default: true }, - { key: 'EVERY_WEEK', label: __('Every week'), default: false }, - { key: 'EVERY_TWO_WEEKS', label: __('Every two weeks'), default: false }, - { key: 'EVERY_MONTH', label: __('Every month'), default: false }, - { key: 'EVERY_THREE_MONTHS', label: __('Every three months'), default: false }, -]; - -export const OLDER_THAN_OPTIONS = [ - { key: 'SEVEN_DAYS', variable: 7, default: false }, - { key: 'FOURTEEN_DAYS', variable: 14, default: false }, - { key: 'THIRTY_DAYS', variable: 30, default: false }, - { key: 'NINETY_DAYS', variable: 90, default: true }, -]; - -export const FETCH_SETTINGS_ERROR_MESSAGE = s__( - 'ContainerRegistry|Something went wrong while fetching the cleanup policy.', -); - -export const UPDATE_SETTINGS_ERROR_MESSAGE = s__( - 'ContainerRegistry|Something went wrong while updating the cleanup policy.', -); - -export const UPDATE_SETTINGS_SUCCESS_MESSAGE = s__( - 'ContainerRegistry|Cleanup policy successfully saved.', -); - -export const NAME_REGEX_LENGTH = 255; diff --git a/app/assets/javascripts/registry/settings/graphql/fragments/container_expiration_policy.fragment.graphql b/app/assets/javascripts/registry/settings/graphql/fragments/container_expiration_policy.fragment.graphql deleted file mode 100644 index 1d6c89133af..00000000000 --- a/app/assets/javascripts/registry/settings/graphql/fragments/container_expiration_policy.fragment.graphql +++ /dev/null @@ -1,9 +0,0 @@ -fragment ContainerExpirationPolicyFields on ContainerExpirationPolicy { - cadence - enabled - keepN - nameRegex - nameRegexKeep - olderThan - nextRunAt -} diff --git a/app/assets/javascripts/registry/settings/graphql/index.js b/app/assets/javascripts/registry/settings/graphql/index.js deleted file mode 100644 index 16152eb81f6..00000000000 --- a/app/assets/javascripts/registry/settings/graphql/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createDefaultClient from '~/lib/graphql'; - -Vue.use(VueApollo); - -export const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient( - {}, - { - assumeImmutableResults: true, - }, - ), -}); diff --git a/app/assets/javascripts/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql b/app/assets/javascripts/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql deleted file mode 100644 index c40cd115ab0..00000000000 --- a/app/assets/javascripts/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql +++ /dev/null @@ -1,10 +0,0 @@ -#import "../fragments/container_expiration_policy.fragment.graphql" - -mutation updateContainerExpirationPolicy($input: UpdateContainerExpirationPolicyInput!) { - updateContainerExpirationPolicy(input: $input) { - containerExpirationPolicy { - ...ContainerExpirationPolicyFields - } - errors - } -} diff --git a/app/assets/javascripts/registry/settings/graphql/queries/get_expiration_policy.query.graphql b/app/assets/javascripts/registry/settings/graphql/queries/get_expiration_policy.query.graphql deleted file mode 100644 index c171be0ad07..00000000000 --- a/app/assets/javascripts/registry/settings/graphql/queries/get_expiration_policy.query.graphql +++ /dev/null @@ -1,9 +0,0 @@ -#import "../fragments/container_expiration_policy.fragment.graphql" - -query getProjectExpirationPolicy($projectPath: ID!) { - project(fullPath: $projectPath) { - containerExpirationPolicy { - ...ContainerExpirationPolicyFields - } - } -} diff --git a/app/assets/javascripts/registry/settings/graphql/utils/cache_update.js b/app/assets/javascripts/registry/settings/graphql/utils/cache_update.js deleted file mode 100644 index c4b2af13862..00000000000 --- a/app/assets/javascripts/registry/settings/graphql/utils/cache_update.js +++ /dev/null @@ -1,21 +0,0 @@ -import { produce } from 'immer'; -import expirationPolicyQuery from '../queries/get_expiration_policy.query.graphql'; - -export const updateContainerExpirationPolicy = (projectPath) => (client, { data: updatedData }) => { - const queryAndParams = { - query: expirationPolicyQuery, - variables: { projectPath }, - }; - const sourceData = client.readQuery(queryAndParams); - - const data = produce(sourceData, (draftState) => { - draftState.project.containerExpirationPolicy = { - ...updatedData.updateContainerExpirationPolicy.containerExpirationPolicy, - }; - }); - - client.writeQuery({ - ...queryAndParams, - data, - }); -}; diff --git a/app/assets/javascripts/registry/settings/registry_settings_bundle.js b/app/assets/javascripts/registry/settings/registry_settings_bundle.js deleted file mode 100644 index 65af6f846aa..00000000000 --- a/app/assets/javascripts/registry/settings/registry_settings_bundle.js +++ /dev/null @@ -1,40 +0,0 @@ -import { GlToast } from '@gitlab/ui'; -import Vue from 'vue'; -import { parseBoolean } from '~/lib/utils/common_utils'; -import Translate from '~/vue_shared/translate'; -import RegistrySettingsApp from './components/registry_settings_app.vue'; -import { apolloProvider } from './graphql/index'; - -Vue.use(GlToast); -Vue.use(Translate); - -export default () => { - const el = document.getElementById('js-registry-settings'); - if (!el) { - return null; - } - const { - isAdmin, - enableHistoricEntries, - projectPath, - adminSettingsPath, - tagsRegexHelpPagePath, - } = el.dataset; - return new Vue({ - el, - apolloProvider, - components: { - RegistrySettingsApp, - }, - provide: { - isAdmin: parseBoolean(isAdmin), - enableHistoricEntries: parseBoolean(enableHistoricEntries), - projectPath, - adminSettingsPath, - tagsRegexHelpPagePath, - }, - render(createElement) { - return createElement('registry-settings-app', {}); - }, - }); -}; diff --git a/app/assets/javascripts/registry/settings/utils.js b/app/assets/javascripts/registry/settings/utils.js deleted file mode 100644 index 4a2d7c7d466..00000000000 --- a/app/assets/javascripts/registry/settings/utils.js +++ /dev/null @@ -1,26 +0,0 @@ -import { n__ } from '~/locale'; -import { KEEP_N_OPTIONS, CADENCE_OPTIONS, OLDER_THAN_OPTIONS } from './constants'; - -export const findDefaultOption = (options) => { - const item = options.find((o) => o.default); - return item ? item.key : null; -}; - -export const olderThanTranslationGenerator = (variable) => n__('%d day', '%d days', variable); - -export const keepNTranslationGenerator = (variable) => - n__('%d tag per image name', '%d tags per image name', variable); - -export const optionLabelGenerator = (collection, translationFn) => - collection.map((option) => ({ - ...option, - label: translationFn(option.variable), - })); - -export const formOptionsGenerator = () => { - return { - olderThan: optionLabelGenerator(OLDER_THAN_OPTIONS, olderThanTranslationGenerator), - cadence: CADENCE_OPTIONS, - keepN: optionLabelGenerator(KEEP_N_OPTIONS, keepNTranslationGenerator), - }; -}; |