Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 19:05:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 19:05:49 +0300
commit43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch)
treedceebdc68925362117480a5d672bcff122fb625b /app/assets/javascripts/packages_and_registries
parent20c84b99005abd1c82101dfeff264ac50d2df211 (diff)
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc42
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_modal.vue (renamed from app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue)42
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue18
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue93
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue9
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue8
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js10
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/index.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue75
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue50
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue59
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue44
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue47
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/index.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/harbor_registry/pages/harbor_tags.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue12
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue92
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue10
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue140
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue21
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue11
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue3
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue80
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js42
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql8
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js23
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql39
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_versions.query.graphql38
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/index.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue121
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue48
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/packages_forwarding_settings.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/constants.js11
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue8
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/expiration_dropdown.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/expiration_input.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/expiration_toggle.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/constants.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/utils.js9
60 files changed, 813 insertions, 460 deletions
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_modal.vue
index 2da8ca2d8a8..7922ff9cce3 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/delete_modal.vue
@@ -1,12 +1,13 @@
<script>
import { GlModal, GlSprintf, GlFormInput } from '@gitlab/ui';
-import { n__ } from '~/locale';
+import { __, n__ } from '~/locale';
import {
REMOVE_TAG_CONFIRMATION_TEXT,
REMOVE_TAGS_CONFIRMATION_TEXT,
DELETE_IMAGE_CONFIRMATION_TITLE,
DELETE_IMAGE_CONFIRMATION_TEXT,
-} from '../../constants';
+} from '../constants';
+import { getImageName } from '../utils';
export default {
components: {
@@ -28,12 +29,13 @@ export default {
},
data() {
return {
- projectPath: '',
+ inputImageName: '',
};
},
computed: {
- imageProjectPath() {
- return this.itemsToBeDeleted[0]?.project?.path;
+ imageName() {
+ const [item] = this.itemsToBeDeleted;
+ return getImageName(item);
},
modalTitle() {
if (this.deleteImage) {
@@ -49,7 +51,7 @@ export default {
if (this.deleteImage) {
return {
message: DELETE_IMAGE_CONFIRMATION_TEXT,
- item: this.imageProjectPath,
+ item: this.imageName,
};
}
if (this.itemsToBeDeleted.length > 1) {
@@ -66,7 +68,13 @@ export default {
};
},
disablePrimaryButton() {
- return this.deleteImage && this.projectPath !== this.imageProjectPath;
+ return this.deleteImage && this.inputImageName !== this.imageName;
+ },
+ primaryActionProps() {
+ return {
+ text: __('Delete'),
+ attributes: { variant: 'danger', disabled: this.disablePrimaryButton },
+ };
},
},
methods: {
@@ -74,25 +82,25 @@ export default {
this.$refs.deleteModal.show();
},
},
+ modal: {
+ cancelAction: {
+ text: __('Cancel'),
+ },
+ },
};
</script>
<template>
<gl-modal
ref="deleteModal"
- modal-id="delete-tag-modal"
+ modal-id="delete-modal"
ok-variant="danger"
size="sm"
- :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
- text: __('Delete'),
- attributes: [{ variant: 'danger' }, { disabled: disablePrimaryButton }],
- } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
- :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
- text: __('Cancel'),
- } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
+ :action-primary="primaryActionProps"
+ :action-cancel="$options.modal.cancelAction"
@primary="$emit('confirmDelete')"
@cancel="$emit('cancelDelete')"
- @change="projectPath = ''"
+ @change="inputImageName = ''"
>
<template #modal-title>{{ modalTitle }}</template>
<p v-if="modalDescription" data-testid="description">
@@ -106,7 +114,7 @@ export default {
</gl-sprintf>
</p>
<div v-if="deleteImage">
- <gl-form-input v-model="projectPath" />
+ <gl-form-input v-model="inputImageName" />
</div>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
index 5d77ff9dc0d..da88f768c03 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
@@ -4,9 +4,10 @@ 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';
+import { formatDate } from '~/lib/utils/datetime_utility';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import {
- UPDATED_AT,
+ CREATED_AT,
CLEANUP_UNSCHEDULED_TEXT,
CLEANUP_SCHEDULED_TEXT,
CLEANUP_ONGOING_TEXT,
@@ -24,6 +25,7 @@ import {
} from '../../constants/index';
import getContainerRepositoryMetadata from '../../graphql/queries/get_container_repository_metadata.query.graphql';
+import { getImageName } from '../../utils';
export default {
name: 'DetailsHeader',
@@ -65,11 +67,11 @@ export default {
visibilityIcon() {
return this.imageDetails?.project?.visibility === 'public' ? 'eye' : 'eye-slash';
},
- timeAgo() {
- return this.timeFormatted(this.imageDetails.updatedAt);
+ formattedCreatedAtDate() {
+ return formatDate(this.imageDetails.createdAt, 'mmm d, yyyy HH:MM', true);
},
- updatedText() {
- return sprintf(UPDATED_AT, { time: this.timeAgo });
+ createdText() {
+ return sprintf(CREATED_AT, { time: this.formattedCreatedAtDate });
},
tagCountText() {
if (this.$apollo.queries.containerRepository.loading) {
@@ -99,7 +101,7 @@ export default {
return !this.imageDetails.name ? ROOT_IMAGE_TOOLTIP : '';
},
imageName() {
- return this.imageDetails.name || this.imageDetails.project?.path;
+ return getImageName(this.imageDetails);
},
formattedSize() {
const { size } = this.imageDetails;
@@ -145,9 +147,9 @@ export default {
<template #metadata-updated>
<metadata-item
:icon="visibilityIcon"
- :text="updatedText"
+ :text="createdText"
size="xl"
- data-testid="updated-and-visibility"
+ data-testid="created-and-visibility"
/>
</template>
<template #right-actions>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
index c10d8be69a0..9ea1958a0d1 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
@@ -1,14 +1,18 @@
<script>
import { GlEmptyState } from '@gitlab/ui';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { n__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
+import Tracking from '~/tracking';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
-
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
import {
+ ALERT_SUCCESS_TAG,
+ ALERT_DANGER_TAG,
+ ALERT_SUCCESS_TAGS,
+ ALERT_DANGER_TAGS,
REMOVE_TAGS_BUTTON_TITLE,
TAGS_LIST_TITLE,
GRAPHQL_PAGE_SIZE,
@@ -20,19 +24,22 @@ import {
NO_TAGS_MATCHING_FILTERS_DESCRIPTION,
} from '../../constants/index';
import getContainerRepositoryTagsQuery from '../../graphql/queries/get_container_repository_tags.query.graphql';
+import deleteContainerRepositoryTagsMutation from '../../graphql/mutations/delete_container_repository_tags.mutation.graphql';
+import DeleteModal from '../delete_modal.vue';
import TagsListRow from './tags_list_row.vue';
export default {
name: 'TagsList',
components: {
+ DeleteModal,
GlEmptyState,
TagsListRow,
TagsLoader,
RegistryList,
PersistedSearch,
},
+ mixins: [Tracking.mixin()],
inject: ['config'],
-
props: {
id: {
type: [Number, String],
@@ -77,6 +84,8 @@ export default {
return {
containerRepository: {},
filters: {},
+ itemsToBeDeleted: [],
+ mutationLoading: false,
sort: null,
};
},
@@ -87,6 +96,9 @@ export default {
tags() {
return this.containerRepository?.tags?.nodes || [];
},
+ hideBulkDelete() {
+ return !this.containerRepository?.canDelete;
+ },
tagsPageInfo() {
return this.containerRepository?.tags?.pageInfo;
},
@@ -98,14 +110,16 @@ export default {
sort: this.sort,
};
},
- showMultiDeleteButton() {
- return this.tags.some((tag) => tag.canDelete) && !this.isMobile;
- },
hasNoTags() {
return this.tags.length === 0;
},
isLoading() {
- return this.isImageLoading || this.$apollo.queries.containerRepository.loading || !this.sort;
+ return (
+ this.isImageLoading ||
+ this.$apollo.queries.containerRepository.loading ||
+ this.mutationLoading ||
+ !this.sort
+ );
},
hasFilters() {
return this.filters?.name;
@@ -116,17 +130,61 @@ export default {
emptyStateDescription() {
return this.hasFilters ? NO_TAGS_MATCHING_FILTERS_DESCRIPTION : NO_TAGS_MESSAGE;
},
+ tracking() {
+ return {
+ label:
+ this.itemsToBeDeleted?.length > 1 ? 'bulk_registry_tag_delete' : 'registry_tag_delete',
+ };
+ },
},
methods: {
+ deleteTags(toBeDeleted) {
+ this.itemsToBeDeleted = toBeDeleted;
+ this.track('click_button');
+ this.$refs.deleteModal.show();
+ },
+ confirmDelete() {
+ this.handleDeleteTag();
+ },
+ async handleDeleteTag() {
+ this.track('confirm_delete');
+ const { itemsToBeDeleted } = this;
+ this.mutationLoading = true;
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: deleteContainerRepositoryTagsMutation,
+ variables: {
+ id: this.queryVariables.id,
+ tagNames: itemsToBeDeleted.map((item) => item.name),
+ },
+ awaitRefetchQueries: true,
+ refetchQueries: [
+ {
+ query: getContainerRepositoryTagsQuery,
+ variables: this.queryVariables,
+ },
+ ],
+ });
+ if (data?.destroyContainerRepositoryTags?.errors[0]) {
+ throw new Error();
+ }
+ this.$emit(
+ 'delete',
+ itemsToBeDeleted.length === 1 ? ALERT_SUCCESS_TAG : ALERT_SUCCESS_TAGS,
+ );
+ this.itemsToBeDeleted = [];
+ } catch (e) {
+ this.$emit('delete', itemsToBeDeleted.length === 1 ? ALERT_DANGER_TAG : ALERT_DANGER_TAGS);
+ } finally {
+ this.mutationLoading = false;
+ }
+ },
fetchNextPage() {
this.$apollo.queries.containerRepository.fetchMore({
variables: {
after: this.tagsPageInfo?.endCursor,
first: GRAPHQL_PAGE_SIZE,
},
- updateQuery(_, { fetchMoreResult }) {
- return fetchMoreResult;
- },
});
},
fetchPreviousPage() {
@@ -136,9 +194,6 @@ export default {
before: this.tagsPageInfo?.startCursor,
last: GRAPHQL_PAGE_SIZE,
},
- updateQuery(_, { fetchMoreResult }) {
- return fetchMoreResult;
- },
});
},
handleSearchUpdate({ sort, filters }) {
@@ -186,13 +241,14 @@ export default {
/>
<template v-else>
<registry-list
+ :hidden-delete="hideBulkDelete"
:title="listTitle"
:pagination="tagsPageInfo"
:items="tags"
id-property="name"
@prev-page="fetchPreviousPage"
@next-page="fetchNextPage"
- @delete="$emit('delete', $event)"
+ @delete="deleteTags"
>
<template #default="{ selectItem, isSelected, item, first }">
<tags-list-row
@@ -202,10 +258,17 @@ export default {
:is-mobile="isMobile"
:disabled="disabled"
@select="selectItem(item)"
- @delete="$emit('delete', [item])"
+ @delete="deleteTags([item])"
/>
</template>
</registry-list>
+
+ <delete-modal
+ ref="deleteModal"
+ :items-to-be-deleted="itemsToBeDeleted"
+ @confirmDelete="confirmDelete"
+ @cancel="track('cancel_delete')"
+ />
</template>
</template>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
index 38b601ac3ec..8e89128a382 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
@@ -109,9 +109,6 @@ export default {
isInvalidTag() {
return !this.tag.digest;
},
- isDeleteDisabled() {
- return this.disabled || !this.tag.canDelete;
- },
},
};
</script>
@@ -179,16 +176,16 @@ export default {
</gl-sprintf>
</span>
</template>
- <template #right-action>
+ <template v-if="tag.canDelete" #right-action>
<gl-dropdown
- :disabled="isDeleteDisabled"
+ :disabled="disabled"
icon="ellipsis_v"
:text="$options.i18n.MORE_ACTIONS_TEXT"
:text-sr-only="true"
category="tertiary"
no-caret
right
- :class="{ 'gl-opacity-0 gl-pointer-events-none': isDeleteDisabled }"
+ :class="{ 'gl-opacity-0 gl-pointer-events-none': disabled }"
data-testid="additional-actions"
data-qa-selector="more_actions_menu"
>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
index 4f89d217623..f6f816f435c 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltipDirective, GlIcon, GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui';
+import { GlTooltipDirective, GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { n__ } from '~/locale';
import Tracking from '~/tracking';
@@ -28,7 +28,6 @@ export default {
DeleteButton,
GlSprintf,
GlButton,
- GlIcon,
ListItem,
GlSkeletonLoader,
CleanupStatus,
@@ -80,8 +79,8 @@ export default {
},
tagsCountText() {
return n__(
- 'ContainerRegistry|%{count} Tag',
- 'ContainerRegistry|%{count} Tags',
+ 'ContainerRegistry|%{count} tag',
+ 'ContainerRegistry|%{count} tags',
this.item.tagsCount,
);
},
@@ -152,7 +151,6 @@ export default {
<span v-if="deleting">{{ $options.i18n.ROW_SCHEDULED_FOR_DELETION }}</span>
<template v-else>
<span class="gl-display-flex gl-align-items-center" data-testid="tags-count">
- <gl-icon name="tag" class="gl-mr-2" />
<gl-sprintf :message="tagsCountText">
<template #count>
{{ item.tagsCount }}
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
index 7bb69363743..7ac803a8ece 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
@@ -65,7 +65,7 @@ export const MISSING_MANIFEST_WARNING_TOOLTIP = s__(
'ContainerRegistry|Invalid tag: missing manifest digest',
);
-export const UPDATED_AT = s__('ContainerRegistry|Last updated %{time}');
+export const CREATED_AT = s__('ContainerRegistry|Created %{time}');
export const NOT_AVAILABLE_TEXT = __('Not applicable.');
export const NOT_AVAILABLE_SIZE = __('0 bytes');
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
index 9d0ecfd2dcb..71538ea5a07 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js
@@ -1,14 +1,10 @@
import { s__ } from '~/locale';
-export const EXPIRATION_POLICY_WILL_RUN_IN = s__(
- 'ContainerRegistry|Expiration policy will run in %{time}',
-);
-export const EXPIRATION_POLICY_DISABLED_TEXT = s__(
- 'ContainerRegistry|Expiration policy is disabled.',
-);
+export const EXPIRATION_POLICY_WILL_RUN_IN = s__('ContainerRegistry|Cleanup will run in %{time}');
+export const EXPIRATION_POLICY_DISABLED_TEXT = s__('ContainerRegistry|Cleanup is not scheduled.');
export const DELETE_ALERT_TITLE = s__('ContainerRegistry|Some tags were not deleted');
export const DELETE_ALERT_LINK_TEXT = s__(
- 'ContainerRegistry|The cleanup policy timed out before it could delete all tags. An administrator can %{adminLinkStart}manually run cleanup now%{adminLinkEnd} or you can wait for the cleanup policy to automatically run again. %{docLinkStart}More information%{docLinkEnd}',
+ 'ContainerRegistry|The cleanup policy timed out before it could delete all tags. An administrator can %{adminLinkStart}run cleanup now manually%{adminLinkEnd} or you can wait for the next scheduled run of the cleanup policy. %{docLinkStart}More information%{docLinkEnd}',
);
export const PARTIAL_CLEANUP_CONTINUE_MESSAGE = s__(
'ContainerRegistry|The cleanup will continue within %{time}. %{linkStart}Learn more%{linkEnd}',
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
index f2aa4916f48..89cdbf6acba 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
@@ -4,6 +4,7 @@ import { NAME_SORT_FIELD } from './common';
// Translations strings
export const CONTAINER_REGISTRY_TITLE = s__('ContainerRegistry|Container Registry');
+export const SETTINGS_TEXT = s__('ContainerRegistry|Configure in settings');
export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection error');
export const CONNECTION_ERROR_MESSAGE = s__(
`ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}.`,
@@ -15,9 +16,6 @@ export const LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION = s__(
`ContainerRegistry|Image repository temporarily cannot be marked for deletion. Please try again in a few minutes. %{docLinkStart}More details%{docLinkEnd}`,
);
export const REMOVE_REPOSITORY_LABEL = s__('ContainerRegistry|Remove repository');
-export const REMOVE_REPOSITORY_MODAL_TEXT = s__(
- 'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
-);
export const ROW_SCHEDULED_FOR_DELETION = s__(
`ContainerRegistry|This image repository is scheduled for deletion`,
);
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/index.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/index.js
index 850dca07a3f..f9820df4a12 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/index.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/index.js
@@ -6,7 +6,6 @@ Vue.use(VueApollo);
export const mergeVariables = (existing, incoming) => {
if (!incoming) return existing;
- if (!existing) return incoming;
return incoming;
};
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
index e2036d9e63d..eae663acb48 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
@@ -7,7 +7,6 @@ query getContainerRepositoryDetails($id: ContainerRepositoryID!) {
location
canDelete
createdAt
- updatedAt
expirationPolicyStartedAt
expirationPolicyCleanupStatus
project {
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
index e57ac2a9efe..a0a80600603 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
@@ -12,6 +12,7 @@ query getContainerRepositoryTags(
containerRepository(id: $id) {
id
tagsCount
+ canDelete
tags(after: $after, before: $before, first: $first, last: $last, name: $name, sort: $sort) {
nodes {
digest
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
index a558550c91f..afddf78203d 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
@@ -36,6 +36,7 @@ export default () => {
isGroupPage,
isAdmin,
showCleanupPolicyLink,
+ showContainerRegistrySettings,
showUnfinishedTagCleanupCallout,
connectionError,
invalidPathError,
@@ -69,6 +70,7 @@ export default () => {
isGroupPage: parseBoolean(isGroupPage),
isAdmin: parseBoolean(isAdmin),
showCleanupPolicyLink: parseBoolean(showCleanupPolicyLink),
+ showContainerRegistrySettings: parseBoolean(showContainerRegistrySettings),
showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout),
connectionError: parseBoolean(connectionError),
invalidPathError: parseBoolean(invalidPathError),
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
index 83c0d2cdfca..3126af69c2c 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
@@ -1,36 +1,28 @@
<script>
import { GlResizeObserverDirective, GlEmptyState } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { joinPaths } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
import DeleteImage from '../components/delete_image.vue';
import DeleteAlert from '../components/details_page/delete_alert.vue';
-import DeleteModal from '../components/details_page/delete_modal.vue';
+import DeleteModal from '../components/delete_modal.vue';
import DetailsHeader from '../components/details_page/details_header.vue';
import PartialCleanupAlert from '../components/details_page/partial_cleanup_alert.vue';
import StatusAlert from '../components/details_page/status_alert.vue';
import TagsList from '../components/details_page/tags_list.vue';
import {
- ALERT_SUCCESS_TAG,
- ALERT_DANGER_TAG,
- ALERT_SUCCESS_TAGS,
- ALERT_DANGER_TAGS,
ALERT_DANGER_IMAGE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
UNFINISHED_STATUS,
MISSING_OR_DELETED_IMAGE_BREADCRUMB,
- GRAPHQL_PAGE_SIZE,
MISSING_OR_DELETED_IMAGE_TITLE,
MISSING_OR_DELETED_IMAGE_MESSAGE,
} from '../constants/index';
-import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.mutation.graphql';
import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.query.graphql';
-import getContainerRepositoryTagsQuery from '../graphql/queries/get_container_repository_tags.query.graphql';
-import getContainerRepositoriesDetails from '../graphql/queries/get_container_repositories_details.query.graphql';
export default {
name: 'RegistryDetailsPage',
@@ -76,7 +68,6 @@ export default {
mutationLoading: false,
deleteAlertType: null,
hidePartialCleanupWarning: false,
- deleteImageAlert: false,
};
},
computed: {
@@ -97,8 +88,7 @@ export default {
},
tracking() {
return {
- label:
- this.itemsToBeDeleted?.length > 1 ? 'bulk_registry_tag_delete' : 'registry_tag_delete',
+ label: 'registry_image_delete',
};
},
pageActionsAreDisabled() {
@@ -112,57 +102,8 @@ export default {
: MISSING_OR_DELETED_IMAGE_BREADCRUMB;
this.breadCrumbState.updateName(name);
},
- deleteTags(toBeDeleted) {
- this.deleteImageAlert = false;
- this.itemsToBeDeleted = toBeDeleted;
- this.track('click_button');
- this.$refs.deleteModal.show();
- },
confirmDelete() {
- if (this.deleteImageAlert) {
- this.$refs.deleteImage.doDelete();
- } else {
- this.handleDeleteTag();
- }
- },
- async handleDeleteTag() {
- this.track('confirm_delete');
- const { itemsToBeDeleted } = this;
- this.itemsToBeDeleted = [];
- this.mutationLoading = true;
- try {
- const { data } = await this.$apollo.mutate({
- mutation: deleteContainerRepositoryTagsMutation,
- variables: {
- id: this.queryVariables.id,
- tagNames: itemsToBeDeleted.map((i) => i.name),
- },
- awaitRefetchQueries: true,
- refetchQueries: [
- {
- query: getContainerRepositoryTagsQuery,
- variables: { ...this.queryVariables, first: GRAPHQL_PAGE_SIZE },
- },
- {
- query: getContainerRepositoriesDetails,
- variables: {
- fullPath: this.config.isGroupPage ? this.config.groupPath : this.config.projectPath,
- isGroupPage: this.config.isGroupPage,
- },
- },
- ],
- });
-
- if (data?.destroyContainerRepositoryTags?.errors[0]) {
- throw new Error();
- }
- this.deleteAlertType =
- itemsToBeDeleted.length === 0 ? ALERT_SUCCESS_TAG : ALERT_SUCCESS_TAGS;
- } catch (e) {
- this.deleteAlertType = itemsToBeDeleted.length === 0 ? ALERT_DANGER_TAG : ALERT_DANGER_TAGS;
- }
-
- this.mutationLoading = false;
+ this.$refs.deleteImage.doDelete();
},
handleResize() {
this.isMobile = GlBreakpointInstance.getBreakpointSize() === 'xs';
@@ -174,7 +115,6 @@ export default {
});
},
deleteImage() {
- this.deleteImageAlert = true;
this.itemsToBeDeleted = [{ ...this.containerRepository }];
this.$refs.deleteModal.show();
},
@@ -185,6 +125,9 @@ export default {
this.itemsToBeDeleted = [];
this.mutationLoading = true;
},
+ showAlert(alertType) {
+ this.deleteAlertType = alertType;
+ },
},
};
</script>
@@ -222,7 +165,7 @@ export default {
:is-image-loading="isLoading"
:is-mobile="isMobile"
:disabled="pageActionsAreDisabled"
- @delete="deleteTags"
+ @delete="showAlert"
/>
<delete-image
@@ -237,7 +180,7 @@ export default {
<delete-modal
ref="deleteModal"
:items-to-be-deleted="itemsToBeDeleted"
- :delete-image="deleteImageAlert"
+ delete-image
@confirmDelete="confirmDelete"
@cancel="track('cancel_delete')"
/>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
index 8a038d7c974..fe29fa8fdd7 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
@@ -1,8 +1,8 @@
<script>
import {
+ GlButton,
GlEmptyState,
GlTooltipDirective,
- GlModal,
GlSprintf,
GlLink,
GlAlert,
@@ -10,31 +10,33 @@ import {
} from '@gitlab/ui';
import { get } from 'lodash';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
+import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import Tracking from '~/tracking';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
import DeleteImage from '../components/delete_image.vue';
import RegistryHeader from '../components/list_page/registry_header.vue';
+import DeleteModal from '../components/delete_modal.vue';
import {
DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
CONNECTION_ERROR_TITLE,
CONNECTION_ERROR_MESSAGE,
- REMOVE_REPOSITORY_MODAL_TEXT,
- REMOVE_REPOSITORY_LABEL,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
GRAPHQL_PAGE_SIZE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
SORT_FIELDS,
+ SETTINGS_TEXT,
} from '../constants/index';
import getContainerRepositoriesDetails from '../graphql/queries/get_container_repositories_details.query.graphql';
export default {
name: 'RegistryListPage',
components: {
+ GlButton,
GlEmptyState,
ProjectEmptyState: () =>
import(
@@ -52,7 +54,7 @@ export default {
import(
/* webpackChunkName: 'container_registry_components' */ '~/packages_and_registries/shared/components/cli_commands.vue'
),
- GlModal,
+ DeleteModal,
GlSprintf,
GlLink,
GlAlert,
@@ -74,10 +76,9 @@ export default {
i18n: {
CONNECTION_ERROR_TITLE,
CONNECTION_ERROR_MESSAGE,
- REMOVE_REPOSITORY_MODAL_TEXT,
- REMOVE_REPOSITORY_LABEL,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
+ SETTINGS_TEXT,
},
searchConfig: SORT_FIELDS,
apollo: {
@@ -144,8 +145,11 @@ export default {
}
return [];
},
+ itemsToBeDeleted() {
+ return this.itemToDelete?.id ? [this.itemToDelete] : [];
+ },
graphqlResource() {
- return this.config.isGroupPage ? 'group' : 'project';
+ return this.config.isGroupPage ? WORKSPACE_GROUP : WORKSPACE_PROJECT;
},
queryVariables() {
return {
@@ -306,6 +310,13 @@ export default {
:docker-push-command="dockerPushCommand"
:docker-login-command="dockerLoginCommand"
/>
+ <gl-button
+ v-if="config.showContainerRegistrySettings"
+ v-gl-tooltip="$options.i18n.SETTINGS_TEXT"
+ icon="settings"
+ :href="config.cleanupPoliciesSettingsPath"
+ :aria-label="$options.i18n.SETTINGS_TEXT"
+ />
</template>
</registry-header>
<persisted-search
@@ -367,26 +378,13 @@ export default {
@end="mutationLoading = false"
>
<template #default="{ doDelete }">
- <gl-modal
+ <delete-modal
ref="deleteModal"
- size="sm"
- modal-id="delete-image-modal"
- :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
- text: __('Remove'),
- attributes: { variant: 'danger' },
- } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
- @primary="doDelete"
+ :items-to-be-deleted="itemsToBeDeleted"
+ delete-image
+ @confirmDelete="doDelete"
@cancel="track('cancel_delete')"
- >
- <template #modal-title>{{ $options.i18n.REMOVE_REPOSITORY_LABEL }}</template>
- <p>
- <gl-sprintf :message="$options.i18n.REMOVE_REPOSITORY_MODAL_TEXT">
- <template #title>
- <b>{{ itemToDelete.path }}</b>
- </template>
- </gl-sprintf>
- </p>
- </gl-modal>
+ />
</template>
</delete-image>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js
index ffdaf9f2f17..751ab5180a1 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js
@@ -1,5 +1,9 @@
import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
+export const getImageName = (image = {}) => {
+ return image.name || image.project?.path;
+};
+
export const timeTilRun = (time) => {
if (!time) return '';
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
index 45dc217b9e3..732d544816b 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
@@ -1,6 +1,7 @@
<script>
import {
GlAlert,
+ GlButton,
GlDropdown,
GlDropdownItem,
GlEmptyState,
@@ -8,8 +9,8 @@ import {
GlFormInputGroup,
GlModal,
GlModalDirective,
- GlSkeletonLoader,
GlSprintf,
+ GlTooltipDirective,
} from '@gitlab/ui';
import { __, s__, n__, sprintf } from '~/locale';
import Api from '~/api';
@@ -24,13 +25,13 @@ import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency
export default {
components: {
GlAlert,
+ GlButton,
GlDropdown,
GlDropdownItem,
GlEmptyState,
GlFormGroup,
GlFormInputGroup,
GlModal,
- GlSkeletonLoader,
GlSprintf,
ClipboardButton,
TitleArea,
@@ -38,8 +39,9 @@ export default {
},
directives: {
GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
- inject: ['groupPath', 'groupId', 'noManifestsIllustration', 'canClearCache'],
+ inject: ['groupPath', 'groupId', 'noManifestsIllustration', 'canClearCache', 'settingsPath'],
i18n: {
proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'),
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
@@ -50,12 +52,13 @@ export default {
'DependencyProxy|All items in the cache are scheduled for removal.',
),
clearCache: s__('DependencyProxy|Clear cache'),
+ settingsText: s__('DependencyProxy|Configure in settings'),
},
confirmClearCacheModal: 'confirm-clear-cache-modal',
modalButtons: {
primary: {
text: s__('DependencyProxy|Clear cache'),
- attributes: [{ variant: 'danger' }],
+ attributes: { variant: 'danger' },
},
secondary: {
text: __('Cancel'),
@@ -114,10 +117,13 @@ export default {
);
},
showDeleteDropdown() {
- return this.group.dependencyProxyManifests?.nodes.length > 0 && this.canClearCache;
+ return this.manifests?.length > 0 && this.canClearCache;
+ },
+ dependencyProxyImagePrefix() {
+ return this.group.dependencyProxyImagePrefix;
},
showDependencyProxyImagePrefix() {
- return this.group.dependencyProxyImagePrefix?.length > 0;
+ return this.dependencyProxyImagePrefix?.length > 0;
},
},
methods: {
@@ -167,8 +173,9 @@ export default {
{{ deleteCacheAlertMessage }}
</gl-alert>
<title-area :title="$options.i18n.pageTitle">
- <template v-if="showDeleteDropdown" #right-actions>
+ <template #right-actions>
<gl-dropdown
+ v-if="showDeleteDropdown"
icon="ellipsis_v"
text="More actions"
:text-sr-only="true"
@@ -181,6 +188,14 @@ export default {
>{{ $options.i18n.clearCache }}</gl-dropdown-item
>
</gl-dropdown>
+ <gl-button
+ v-if="canClearCache"
+ v-gl-tooltip="$options.i18n.settingsText"
+ icon="settings"
+ data-testid="settings-link"
+ :href="settingsPath"
+ :aria-label="$options.i18n.settingsText"
+ />
</template>
</title-area>
@@ -208,23 +223,21 @@ export default {
</template>
</gl-form-group>
- <gl-skeleton-loader v-if="$apollo.queries.group.loading" />
-
- <div v-else data-testid="main-area">
- <manifests-list
- v-if="manifests && manifests.length"
- :manifests="manifests"
- :pagination="pageInfo"
- @prev-page="fetchPreviousPage"
- @next-page="fetchNextPage"
- />
+ <manifests-list
+ v-if="manifests && manifests.length"
+ :dependency-proxy-image-prefix="dependencyProxyImagePrefix"
+ :loading="$apollo.queries.group.loading"
+ :manifests="manifests"
+ :pagination="pageInfo"
+ @prev-page="fetchPreviousPage"
+ @next-page="fetchNextPage"
+ />
- <gl-empty-state
- v-else
- :svg-path="noManifestsIllustration"
- :title="$options.i18n.noManifestTitle"
- />
- </div>
+ <gl-empty-state
+ v-else
+ :svg-path="noManifestsIllustration"
+ :title="$options.i18n.noManifestTitle"
+ />
<gl-modal
:modal-id="$options.confirmClearCacheModal"
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
index 1bbd0c32dc4..254fd578cf1 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue
@@ -3,11 +3,16 @@ import { GlIcon, GlSprintf } from '@gitlab/ui';
import { MANIFEST_PENDING_DESTRUCTION_STATUS } from '~/packages_and_registries/dependency_proxy/constants';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { s__ } from '~/locale';
+const SHORT_DIGEST_START_INDEX = 7;
+const SHORT_DIGEST_END_INDEX = 14;
+
export default {
name: 'ManifestRow',
components: {
+ ClipboardButton,
GlIcon,
GlSprintf,
ListItem,
@@ -18,13 +23,25 @@ export default {
type: Object,
required: true,
},
+ dependencyProxyImagePrefix: {
+ type: String,
+ default: '',
+ required: false,
+ },
},
computed: {
name() {
- return this.manifest?.imageName.split(':')[0];
+ if (this.containsDigestInImageName) {
+ return this.manifest?.imageName.split(':')[0];
+ }
+ return this.manifest?.imageName;
},
- version() {
- return this.manifest?.imageName.split(':')[1];
+ imageCopyText() {
+ const name = this.manifest?.imageName.replace(':sha256:', '@sha256:') ?? '';
+ return `${this.dependencyProxyImagePrefix}/${name}`;
+ },
+ containsDigestInImageName() {
+ return this.manifest?.imageName.includes(':sha256:');
},
isErrorStatus() {
return this.manifest?.status === MANIFEST_PENDING_DESTRUCTION_STATUS;
@@ -32,9 +49,16 @@ export default {
disabledRowStyle() {
return this.isErrorStatus ? 'gl-font-weight-normal gl-text-gray-500' : '';
},
+ shortDigest() {
+ // digest is in the format `sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089`
+ // for short digest, remove sha256: from the string, and show only the first 7 char
+ return this.manifest.digest?.substring(SHORT_DIGEST_START_INDEX, SHORT_DIGEST_END_INDEX);
+ },
},
i18n: {
cachedAgoMessage: s__('DependencyProxy|Cached %{time}'),
+ copyImagePathTitle: s__('DependencyProxy|Copy image path'),
+ digestLabel: s__('DependencyProxy|Digest: %{shortDigest}'),
scheduledForDeletion: s__('DependencyProxy|Scheduled for deletion'),
},
};
@@ -44,9 +68,21 @@ export default {
<list-item :disabled="isErrorStatus">
<template #left-primary>
<span :class="disabledRowStyle">{{ name }}</span>
+ <clipboard-button
+ class="gl-ml-2"
+ :text="imageCopyText"
+ :title="$options.i18n.copyImagePathTitle"
+ category="tertiary"
+ />
</template>
<template #left-secondary>
- {{ version }}
+ <span data-testid="manifest-row-short-digest">
+ <gl-sprintf :message="$options.i18n.digestLabel">
+ <template #shortDigest>
+ {{ shortDigest }}
+ </template>
+ </gl-sprintf>
+ </span>
<span v-if="isErrorStatus" class="gl-ml-4" data-testid="status"
><gl-icon name="clock" /> {{ $options.i18n.scheduledForDeletion }}</span
>
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
index 005c8feea3a..9870841f1ff 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue
@@ -1,5 +1,5 @@
<script>
-import { GlKeysetPagination } from '@gitlab/ui';
+import { GlKeysetPagination, GlSkeletonLoader } from '@gitlab/ui';
import { s__ } from '~/locale';
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
@@ -8,6 +8,7 @@ export default {
components: {
ManifestRow,
GlKeysetPagination,
+ GlSkeletonLoader,
},
props: {
manifests: {
@@ -19,6 +20,16 @@ export default {
type: Object,
required: true,
},
+ loading: {
+ type: Boolean,
+ required: false,
+ default: () => false,
+ },
+ dependencyProxyImagePrefix: {
+ type: String,
+ default: '',
+ required: false,
+ },
},
i18n: {
listTitle: s__('DependencyProxy|Image list'),
@@ -34,19 +45,27 @@ export default {
<template>
<div class="gl-mt-6">
<h3 class="gl-font-base">{{ $options.i18n.listTitle }}</h3>
- <div
- class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column"
- >
- <manifest-row v-for="(manifest, index) in manifests" :key="index" :manifest="manifest" />
- </div>
- <div class="gl-display-flex gl-justify-content-center">
- <gl-keyset-pagination
- v-if="showPagination"
- v-bind="pagination"
- class="gl-mt-3"
- @prev="$emit('prev-page')"
- @next="$emit('next-page')"
- />
+ <gl-skeleton-loader v-if="loading" />
+ <div v-else data-testid="main-area">
+ <div
+ class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column"
+ >
+ <manifest-row
+ v-for="(manifest, index) in manifests"
+ :key="index"
+ :dependency-proxy-image-prefix="dependencyProxyImagePrefix"
+ :manifest="manifest"
+ />
+ </div>
+ <div class="gl-display-flex gl-justify-content-center">
+ <gl-keyset-pagination
+ v-if="showPagination"
+ v-bind="pagination"
+ class="gl-mt-3"
+ @prev="$emit('prev-page')"
+ @next="$emit('next-page')"
+ />
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
index c1597625964..db0e596ba64 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
@@ -19,6 +19,7 @@ query getDependencyProxyDetails(
nodes {
id
createdAt
+ digest
imageName
status
}
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
index 428d6d6cd75..74444d2c7ec 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
@@ -11,7 +11,7 @@ export const initDependencyProxyApp = () => {
if (!el) {
return null;
}
- const { groupPath, groupId, noManifestsIllustration, canClearCache } = el.dataset;
+ const { groupPath, groupId, noManifestsIllustration, canClearCache, settingsPath } = el.dataset;
return new Vue({
el,
apolloProvider,
@@ -20,6 +20,7 @@ export const initDependencyProxyApp = () => {
groupId,
noManifestsIllustration,
canClearCache: parseBoolean(canClearCache),
+ settingsPath,
},
render(createElement) {
return createElement(app);
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue
index bafcd78ad5d..bff32a124bc 100644
--- a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/details.vue
@@ -9,7 +9,7 @@ import {
TAG_LABEL,
} from '~/packages_and_registries/harbor_registry/constants/index';
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
import ArtifactsList from '~/packages_and_registries/harbor_registry/components/details/artifacts_list.vue';
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/harbor_tags.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/harbor_tags.vue
index 1323d347d10..8bc1ecba5fe 100644
--- a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/harbor_tags.vue
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/harbor_tags.vue
@@ -4,7 +4,7 @@ import TagsList from '~/packages_and_registries/harbor_registry/components/tags/
import { getHarborTags } from '~/rest_api';
import { FETCH_TAGS_ERROR_MESSAGE } from '~/packages_and_registries/harbor_registry/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { formatPagination } from '~/packages_and_registries/harbor_registry/utils';
export default {
diff --git a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue
index 931a99649cb..1d8cb0f1360 100644
--- a/app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/harbor_registry/pages/list.vue
@@ -12,7 +12,7 @@ import {
dockerPushCommand,
dockerLoginCommand,
} from '~/packages_and_registries/harbor_registry/utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import {
SORT_FIELDS,
CONNECTION_ERROR_TITLE,
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
index fd099ee4e69..fdc58e4bd05 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
@@ -122,15 +122,15 @@ export default {
modal: {
packageDeletePrimaryAction: {
text: __('Delete'),
- attributes: [
- { variant: 'danger' },
- { category: 'primary' },
- { 'data-qa-selector': 'delete_modal_button' },
- ],
+ attributes: {
+ variant: 'danger',
+ category: 'primary',
+ 'data-qa-selector': 'delete_modal_button',
+ },
},
fileDeletePrimaryAction: {
text: __('Delete'),
- attributes: [{ variant: 'danger' }, { category: 'primary' }],
+ attributes: { variant: 'danger', category: 'primary' },
},
cancelAction: {
text: __('Cancel'),
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
index 223f427ce0e..62c4f96eff7 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
@@ -1,5 +1,5 @@
import Api from '~/api';
-import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert';
import {
DELETE_PACKAGE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE,
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue
index 9bab08b8548..a9d076afb92 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue
@@ -36,7 +36,7 @@ export default {
},
},
i18n: {
- LIST_TITLE_TEXT: s__('InfrastructureRegistry|Infrastructure Registry'),
+ LIST_TITLE_TEXT: s__('InfrastructureRegistry|Terraform Module Registry'),
LIST_INTRO_TEXT: s__(
'InfrastructureRegistry|Publish and share your modules. %{docLinkStart}More information%{docLinkEnd}',
),
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue
index 0aeeb2c3d15..6ea1fff9ef0 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue
@@ -1,7 +1,7 @@
<script>
import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
-import { createAlert, VARIANT_INFO } from '~/flash';
+import { createAlert, VARIANT_INFO } from '~/alert';
import { historyReplaceState } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages_and_registries/shared/constants';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
index 7af3fc1c2db..05673215a66 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
@@ -6,7 +6,6 @@ export const FETCH_PACKAGES_LIST_ERROR_MESSAGE = __(
export const DELETE_PACKAGE_SUCCESS_MESSAGE = __('Package deleted successfully');
export const DEFAULT_PAGE = 1;
-export const DEFAULT_PAGE_SIZE = 20;
export const GROUP_PAGE_TYPE = 'groups';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
index 7a452abdc26..122123f49cd 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
@@ -1,13 +1,13 @@
import Api from '~/api';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
+import { DEFAULT_PAGE_SIZE } from '~/vue_shared/issuable/list/constants';
import {
FETCH_PACKAGES_LIST_ERROR_MESSAGE,
DELETE_PACKAGE_SUCCESS_MESSAGE,
DEFAULT_PAGE,
- DEFAULT_PAGE_SIZE,
MISSING_DELETE_PATH_ERROR,
TERRAFORM_SEARCH_TYPE,
} from '../constants';
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue
index 011a2668a8b..0c3494ea812 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/delete_modal.vue
@@ -1,39 +1,71 @@
<script>
-import { GlModal } from '@gitlab/ui';
-import { __, n__ } from '~/locale';
+import { GlLink, GlModal, GlSprintf } from '@gitlab/ui';
+import { __ } from '~/locale';
import {
+ DELETE_MODAL_CONTENT,
+ DELETE_MODAL_TITLE,
+ DELETE_PACKAGES_MODAL_DESCRIPTION,
DELETE_PACKAGES_MODAL_TITLE,
DELETE_PACKAGE_MODAL_PRIMARY_ACTION,
+ DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT,
+ DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT,
+ DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION,
+ DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION,
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
} from '~/packages_and_registries/package_registry/constants';
export default {
name: 'DeleteModal',
- i18n: {
- DELETE_PACKAGES_MODAL_TITLE,
- },
components: {
+ GlLink,
GlModal,
+ GlSprintf,
},
props: {
itemsToBeDeleted: {
type: Array,
required: true,
},
+ showRequestForwardingContent: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
- description() {
- return n__(
- 'PackageRegistry|You are about to delete 1 package. This operation is irreversible.',
- `PackageRegistry|You are about to delete %d packages. This operation is irreversible.`,
- this.itemsToBeDeleted.length,
- );
+ itemToBeDeleted() {
+ return this.itemsToBeDeleted.length === 1 ? this.itemsToBeDeleted[0] : null;
+ },
+ title() {
+ return this.itemToBeDeleted ? DELETE_MODAL_TITLE : DELETE_PACKAGES_MODAL_TITLE;
+ },
+ packageDescription() {
+ return this.showRequestForwardingContent
+ ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT
+ : DELETE_MODAL_CONTENT;
+ },
+ packagesDescription() {
+ return this.showRequestForwardingContent
+ ? DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT
+ : DELETE_PACKAGES_MODAL_DESCRIPTION;
+ },
+ packagesDeletePrimaryActionProps() {
+ let text = DELETE_PACKAGE_MODAL_PRIMARY_ACTION;
+
+ if (this.showRequestForwardingContent) {
+ if (this.itemToBeDeleted) {
+ text = DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION;
+ } else {
+ text = DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION;
+ }
+ }
+ return {
+ text,
+ attributes: { variant: 'danger', category: 'primary' },
+ };
},
},
modal: {
- packagesDeletePrimaryAction: {
- text: DELETE_PACKAGE_MODAL_PRIMARY_ACTION,
- attributes: [{ variant: 'danger' }, { category: 'primary' }],
- },
cancelAction: {
text: __('Cancel'),
},
@@ -43,6 +75,9 @@ export default {
this.$refs.deleteModal.show();
},
},
+ links: {
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
+ },
};
</script>
@@ -51,12 +86,33 @@ export default {
ref="deleteModal"
size="sm"
modal-id="delete-packages-modal"
- :action-primary="$options.modal.packagesDeletePrimaryAction"
+ :action-primary="packagesDeletePrimaryActionProps"
:action-cancel="$options.modal.cancelAction"
- :title="$options.i18n.DELETE_PACKAGES_MODAL_TITLE"
+ :title="title"
@primary="$emit('confirm')"
@cancel="$emit('cancel')"
>
- <span>{{ description }}</span>
+ <p>
+ <gl-sprintf v-if="itemToBeDeleted" :message="packageDescription">
+ <template v-if="showRequestForwardingContent" #docLink="{ content }">
+ <gl-link :href="$options.links.REQUEST_FORWARDING_HELP_PAGE_PATH">{{ content }}</gl-link>
+ </template>
+ <template #version>
+ <strong>{{ itemToBeDeleted.version }}</strong>
+ </template>
+ <template #name>
+ <strong>{{ itemToBeDeleted.name }}</strong>
+ </template>
+ </gl-sprintf>
+ <gl-sprintf v-else :message="packagesDescription">
+ <template v-if="showRequestForwardingContent" #docLink="{ content }">
+ <gl-link :href="$options.links.REQUEST_FORWARDING_HELP_PAGE_PATH">{{ content }}</gl-link>
+ </template>
+
+ <template #count>
+ {{ itemsToBeDeleted.length }}
+ </template>
+ </gl-sprintf>
+ </p>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
index 4510c7a7322..95b83d87792 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
@@ -13,6 +13,7 @@ import {
TRACKING_LABEL_CODE_INSTRUCTION,
TRACKING_LABEL_MAVEN_INSTALLATION,
MAVEN_HELP_PATH,
+ MAVEN_INSTALLATION_COMMAND,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -55,11 +56,6 @@ export default {
<version>${this.appVersion}</version>
</dependency>`;
},
-
- mavenInstallationCommand() {
- return `mvn dependency:get -Dartifact=${this.appGroup}:${this.appName}:${this.appVersion}`;
- },
-
mavenSetupXml() {
return `<repositories>
<repository>
@@ -135,6 +131,7 @@ export default {
{ value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') },
{ value: 'kotlin', label: s__('PackageRegistry|Gradle Kotlin DSL') },
],
+ MAVEN_INSTALLATION_COMMAND,
};
</script>
@@ -164,8 +161,9 @@ export default {
/>
<code-instruction
+ class="gl-w-20 gl-mt-5"
:label="s__('PackageRegistry|Maven Command')"
- :instruction="mavenInstallationCommand"
+ :instruction="$options.MAVEN_INSTALLATION_COMMAND"
:copy-text="s__('PackageRegistry|Copy Maven command')"
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue
index d982df4f984..482249bc252 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_versions_list.vue
@@ -1,19 +1,29 @@
<script>
+import { GlAlert } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import { n__ } from '~/locale';
import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
import {
+ CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
+ DELETE_PACKAGE_VERSION_TRACKING_ACTION,
DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
+ FETCH_PACKAGE_VERSIONS_ERROR_MESSAGE,
+ GRAPHQL_PAGE_SIZE,
+ REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
} from '~/packages_and_registries/package_registry/constants';
import Tracking from '~/tracking';
+import { packageTypeToTrackCategory } from '~/packages_and_registries/package_registry/utils';
+import getPackageVersionsQuery from '~/packages_and_registries/package_registry/graphql/queries/get_package_versions.query.graphql';
export default {
components: {
DeleteModal,
+ GlAlert,
VersionRow,
PackagesListLoader,
RegistryList,
@@ -25,49 +35,139 @@ export default {
required: false,
default: false,
},
- versions: {
- type: Array,
- required: true,
- default: () => [],
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
},
- pageInfo: {
- type: Object,
- required: true,
+ isMutationLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- isLoading: {
+ isRequestForwardingEnabled: {
type: Boolean,
required: false,
default: false,
},
+ packageId: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
itemsToBeDeleted: [],
+ packageVersions: {},
+ fetchPackageVersionsError: false,
};
},
+ apollo: {
+ packageVersions: {
+ query: getPackageVersionsQuery,
+ variables() {
+ return this.queryVariables;
+ },
+ skip() {
+ return this.isListEmpty;
+ },
+ update(data) {
+ return data.package?.versions ?? {};
+ },
+ error(error) {
+ this.fetchPackageVersionsError = true;
+ Sentry.captureException(error);
+ },
+ },
+ },
computed: {
+ itemToBeDeleted() {
+ return this.itemsToBeDeleted.length === 1 ? this.itemsToBeDeleted[0] : null;
+ },
+ isListEmpty() {
+ return this.count === 0;
+ },
+ isLoading() {
+ return this.$apollo.queries.packageVersions.loading || this.isMutationLoading;
+ },
+ pageInfo() {
+ return this.packageVersions?.pageInfo ?? {};
+ },
listTitle() {
return n__('%d version', '%d versions', this.versions.length);
},
- isListEmpty() {
- return this.versions.length === 0;
+ queryVariables() {
+ return {
+ id: this.packageId,
+ first: GRAPHQL_PAGE_SIZE,
+ };
+ },
+ tracking() {
+ const category = this.itemToBeDeleted
+ ? packageTypeToTrackCategory(this.itemToBeDeleted.packageType)
+ : undefined;
+ return {
+ category,
+ };
+ },
+ versions() {
+ return this.packageVersions?.nodes ?? [];
},
},
methods: {
deleteItemsCanceled() {
- this.track(CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ if (this.itemToBeDeleted) {
+ this.track(CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION);
+ } else {
+ this.track(CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ }
+
this.itemsToBeDeleted = [];
},
deleteItemsConfirmation() {
this.$emit('delete', this.itemsToBeDeleted);
- this.track(DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ if (this.itemToBeDeleted) {
+ this.track(DELETE_PACKAGE_VERSION_TRACKING_ACTION);
+ } else {
+ this.track(DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ }
this.itemsToBeDeleted = [];
},
setItemsToBeDeleted(items) {
this.itemsToBeDeleted = items;
- this.track(REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ if (items.length === 1) {
+ this.track(REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION);
+ } else {
+ this.track(REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION);
+ }
this.$refs.deletePackagesModal.show();
},
+ fetchPreviousVersionsPage() {
+ const variables = {
+ ...this.queryVariables,
+ first: null,
+ last: GRAPHQL_PAGE_SIZE,
+ before: this.pageInfo?.startCursor,
+ };
+ this.$apollo.queries.packageVersions.fetchMore({
+ variables,
+ });
+ },
+ fetchNextVersionsPage() {
+ const variables = {
+ ...this.queryVariables,
+ first: GRAPHQL_PAGE_SIZE,
+ last: null,
+ after: this.pageInfo?.endCursor,
+ };
+
+ this.$apollo.queries.packageVersions.fetchMore({
+ variables,
+ });
+ },
+ },
+ i18n: {
+ errorMessage: FETCH_PACKAGE_VERSIONS_ERROR_MESSAGE,
},
};
</script>
@@ -76,6 +176,9 @@ export default {
<div v-if="isLoading">
<packages-list-loader />
</div>
+ <gl-alert v-else-if="fetchPackageVersionsError" variant="danger" :dismissible="false">{{
+ $options.i18n.errorMessage
+ }}</gl-alert>
<slot v-else-if="isListEmpty" name="empty-state"></slot>
<div v-else>
<registry-list
@@ -85,17 +188,15 @@ export default {
:pagination="pageInfo"
:title="listTitle"
@delete="setItemsToBeDeleted"
- @prev-page="$emit('prev-page')"
- @next-page="$emit('next-page')"
+ @prev-page="fetchPreviousVersionsPage"
+ @next-page="fetchNextVersionsPage"
>
<template #default="{ first, item, isSelected, selectItem }">
- <!-- `first` prop is used to decide whether to show the top border
- for the first element. We want to show the top border only when
- user has permission to bulk delete versions. -->
<version-row
- :first="canDestroy && first"
+ :first="first"
:package-entity="item"
:selected="isSelected(item)"
+ @delete="setItemsToBeDeleted([item])"
@select="selectItem(item)"
/>
</template>
@@ -104,6 +205,7 @@ export default {
<delete-modal
ref="deletePackagesModal"
:items-to-be-deleted="itemsToBeDeleted"
+ :show-request-forwarding-content="isRequestForwardingEnabled"
@confirm="deleteItemsConfirmation"
@cancel="deleteItemsCanceled"
/>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
index fdc6e75c932..ea6ebb614f4 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
@@ -28,6 +28,9 @@ export default {
},
},
computed: {
+ isPrivatePackage() {
+ return !this.packageEntity.publicPackage;
+ },
pypiPipCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `pip install ${this.packageEntity.name} --index-url ${this.packageEntity.pypiUrl}`;
@@ -75,7 +78,7 @@ password = <your personal access token>`;
:tracking-action="$options.tracking.TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND"
:tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
/>
- <template #description>
+ <template v-if="isPrivatePackage" #description>
<gl-sprintf :message="$options.i18n.tokenText">
<template #link="{ content }">
<gl-link
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
index 9f8f6328970..37a6fe75f15 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
@@ -1,5 +1,7 @@
<script>
import {
+ GlDropdown,
+ GlDropdownItem,
GlFormCheckbox,
GlIcon,
GlLink,
@@ -13,6 +15,7 @@ import PublishMethod from '~/packages_and_registries/shared/components/publish_m
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import {
+ DELETE_PACKAGE_TEXT,
ERRORED_PACKAGE_TEXT,
ERROR_PUBLISHING,
PACKAGE_ERROR_STATUS,
@@ -22,6 +25,8 @@ import {
export default {
name: 'PackageVersionRow',
components: {
+ GlDropdown,
+ GlDropdownItem,
GlFormCheckbox,
GlIcon,
GlLink,
@@ -58,6 +63,7 @@ export default {
},
},
i18n: {
+ deletePackage: DELETE_PACKAGE_TEXT,
erroredPackageText: ERRORED_PACKAGE_TEXT,
errorPublishing: ERROR_PUBLISHING,
warningText: WARNING_TEXT,
@@ -121,5 +127,20 @@ export default {
</gl-sprintf>
</span>
</template>
+
+ <template v-if="packageEntity.canDestroy" #right-action>
+ <gl-dropdown
+ data-testid="delete-dropdown"
+ icon="ellipsis_v"
+ :text="$options.i18n.moreActions"
+ :text-sr-only="true"
+ category="tertiary"
+ no-caret
+ >
+ <gl-dropdown-item data-testid="action-delete" variant="danger" @click="$emit('delete')">{{
+ $options.i18n.deletePackage
+ }}</gl-dropdown-item>
+ </gl-dropdown>
+ </template>
</list-item>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue
index 0914c013108..b7e66d20e78 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/functional/delete_packages.vue
@@ -1,6 +1,6 @@
<script>
import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql';
-import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert';
import {
DELETE_PACKAGE_ERROR_MESSAGE,
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
index 16f21bfe61d..4ec83a869b3 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
@@ -8,9 +8,10 @@ import {
GlTooltipDirective,
GlTruncate,
} from '@gitlab/ui';
-import { s__, __ } from '~/locale';
+import { __ } from '~/locale';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import {
+ DELETE_PACKAGE_TEXT,
ERRORED_PACKAGE_TEXT,
ERROR_PUBLISHING,
PACKAGE_ERROR_STATUS,
@@ -21,7 +22,6 @@ import { getPackageTypeLabel } from '~/packages_and_registries/package_registry/
import PackagePath from '~/packages_and_registries/shared/components/package_path.vue';
import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
import PublishMethod from '~/packages_and_registries/package_registry/components/list/publish_method.vue';
-import PackageIconAndName from '~/packages_and_registries/shared/components/package_icon_and_name.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -38,7 +38,6 @@ export default {
PackagePath,
PublishMethod,
ListItem,
- PackageIconAndName,
TimeagoTooltip,
},
directives: {
@@ -91,7 +90,7 @@ export default {
i18n: {
erroredPackageText: ERRORED_PACKAGE_TEXT,
createdAt: __('Created %{timestamp}'),
- deletePackage: s__('PackageRegistry|Delete package'),
+ deletePackage: DELETE_PACKAGE_TEXT,
errorPublishing: ERROR_PUBLISHING,
warning: WARNING_TEXT,
moreActions: __('More actions'),
@@ -150,9 +149,7 @@ export default {
</gl-sprintf>
</div>
- <package-icon-and-name>
- {{ packageType }}
- </package-icon-and-name>
+ <span class="gl-ml-2" data-testid="package-type">&middot; {{ packageType }}</span>
<package-path
v-if="isGroupPage"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
index 440e11a99f2..05359128af4 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
@@ -39,5 +39,8 @@ export default {
<template #metadata-amount>
<metadata-item v-if="showPackageCount" icon="package" :text="packageAmountText" />
</template>
+ <template #right-actions>
+ <slot name="settings-link"></slot>
+ </template>
</title-area>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
index 486ab4fdc99..effed4891d8 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
@@ -1,7 +1,6 @@
<script>
import { GlAlert } from '@gitlab/ui';
import { s__, sprintf, n__ } from '~/locale';
-import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
@@ -14,16 +13,24 @@ import {
CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
CANCEL_DELETE_PACKAGES_TRACKING_ACTION,
PACKAGE_ERROR_STATUS,
+ PACKAGE_TYPE_MAVEN,
+ PACKAGE_TYPE_NPM,
+ PACKAGE_TYPE_PYPI,
} from '~/packages_and_registries/package_registry/constants';
import { packageTypeToTrackCategory } from '~/packages_and_registries/package_registry/utils';
import Tracking from '~/tracking';
+const forwardingFieldToPackageTypeMapping = {
+ mavenPackageRequestsForwarding: PACKAGE_TYPE_MAVEN,
+ npmPackageRequestsForwarding: PACKAGE_TYPE_NPM,
+ pypiPackageRequestsForwarding: PACKAGE_TYPE_PYPI,
+};
+
export default {
name: 'PackagesList',
components: {
GlAlert,
DeleteModal,
- DeletePackageModal,
PackagesListLoader,
PackagesListRow,
RegistryList,
@@ -44,16 +51,27 @@ export default {
type: Object,
required: true,
},
+ groupSettings: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
},
data() {
return {
- itemToBeDeleted: null,
itemsToBeDeleted: [],
errorPackages: [],
};
},
computed: {
+ itemToBeDeleted() {
+ if (this.itemsToBeDeleted.length === 1) {
+ const [itemToBeDeleted] = this.itemsToBeDeleted;
+ return itemToBeDeleted;
+ }
+ return null;
+ },
listTitle() {
return n__('%d package', '%d packages', this.list.length);
},
@@ -77,6 +95,15 @@ export default {
showErrorPackageAlert() {
return this.errorPackages.length > 0;
},
+ packageTypesWithForwardingEnabled() {
+ return Object.keys(this.groupSettings)
+ .filter((field) => this.groupSettings[field])
+ .map((field) => forwardingFieldToPackageTypeMapping[field]);
+ },
+ isRequestForwardingEnabled() {
+ const selectedPackageTypes = new Set(this.itemsToBeDeleted.map((item) => item.packageType));
+ return this.packageTypesWithForwardingEnabled.some((type) => selectedPackageTypes.has(type));
+ },
},
watch: {
list(newVal) {
@@ -88,40 +115,36 @@ export default {
this.list.length > 0 ? this.list.filter((pkg) => pkg.status === PACKAGE_ERROR_STATUS) : [];
},
methods: {
- setItemToBeDeleted(item) {
- this.itemToBeDeleted = { ...item };
- this.track(REQUEST_DELETE_PACKAGE_TRACKING_ACTION);
- },
setItemsToBeDeleted(items) {
+ this.itemsToBeDeleted = items;
if (items.length === 1) {
- const [item] = items;
- this.setItemToBeDeleted(item);
- return;
+ this.track(REQUEST_DELETE_PACKAGE_TRACKING_ACTION);
+ } else {
+ this.track(REQUEST_DELETE_PACKAGES_TRACKING_ACTION);
}
- this.itemsToBeDeleted = items;
- this.track(REQUEST_DELETE_PACKAGES_TRACKING_ACTION);
this.$refs.deletePackagesModal.show();
},
deleteItemsConfirmation() {
this.$emit('delete', this.itemsToBeDeleted);
- this.track(DELETE_PACKAGES_TRACKING_ACTION);
+
+ if (this.itemToBeDeleted) {
+ this.track(DELETE_PACKAGE_TRACKING_ACTION);
+ } else {
+ this.track(DELETE_PACKAGES_TRACKING_ACTION);
+ }
+
this.itemsToBeDeleted = [];
},
deleteItemsCanceled() {
- this.track(CANCEL_DELETE_PACKAGES_TRACKING_ACTION);
+ if (this.itemToBeDeleted) {
+ this.track(CANCEL_DELETE_PACKAGE_TRACKING_ACTION);
+ } else {
+ this.track(CANCEL_DELETE_PACKAGES_TRACKING_ACTION);
+ }
this.itemsToBeDeleted = [];
},
- deleteItemConfirmation() {
- this.$emit('delete', [this.itemToBeDeleted]);
- this.track(DELETE_PACKAGE_TRACKING_ACTION);
- this.itemToBeDeleted = null;
- },
- deleteItemCanceled() {
- this.track(CANCEL_DELETE_PACKAGE_TRACKING_ACTION);
- this.itemToBeDeleted = null;
- },
showConfirmationModal() {
- this.setItemToBeDeleted(this.errorPackages[0]);
+ this.setItemsToBeDeleted([this.errorPackages[0]]);
},
},
i18n: {
@@ -165,21 +188,16 @@ export default {
:first="first"
:package-entity="item"
:selected="isSelected(item)"
- @delete="setItemToBeDeleted(item)"
+ @delete="setItemsToBeDeleted([item])"
@select="selectItem(item)"
/>
</template>
</registry-list>
- <delete-package-modal
- :item-to-be-deleted="itemToBeDeleted"
- @ok="deleteItemConfirmation"
- @cancel="deleteItemCanceled"
- />
-
<delete-modal
ref="deletePackagesModal"
:items-to-be-deleted="itemsToBeDeleted"
+ :show-request-forwarding-content="isRequestForwardingEnabled"
@confirm="deleteItemsConfirmation"
@cancel="deleteItemsCanceled"
/>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
index d979ae5c08c..b4276d69ed6 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -27,15 +27,8 @@ export const PACKAGE_TYPE_DEBIAN = 'DEBIAN';
export const PACKAGE_TYPE_HELM = 'HELM';
export const TRACKING_LABEL_CODE_INSTRUCTION = 'code_instruction';
-export const TRACKING_LABEL_CONAN_INSTALLATION = 'conan_installation';
export const TRACKING_LABEL_MAVEN_INSTALLATION = 'maven_installation';
-export const TRACKING_LABEL_NPM_INSTALLATION = 'npm_installation';
-export const TRACKING_LABEL_NUGET_INSTALLATION = 'nuget_installation';
-export const TRACKING_LABEL_PYPI_INSTALLATION = 'pypi_installation';
-export const TRACKING_LABEL_COMPOSER_INSTALLATION = 'composer_installation';
-
-export const TRACKING_ACTION_INSTALLATION = 'installation';
-export const TRACKING_ACTION_REGISTRY_SETUP = 'registry_setup';
+export const MAVEN_INSTALLATION_COMMAND = 'mvn install';
export const TRACKING_ACTION_COPY_CONAN_COMMAND = 'copy_conan_command';
export const TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND = 'copy_conan_setup_command';
@@ -68,7 +61,6 @@ export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND =
export const TRACKING_LABEL_PACKAGE_ASSET = 'package_assets';
-export const TRACKING_ACTION_DOWNLOAD_PACKAGE_ASSET = 'download_package_asset';
export const TRACKING_ACTION_EXPAND_PACKAGE_ASSET = 'expand_package_asset';
export const TRACKING_ACTION_COPY_PACKAGE_ASSET_SHA = 'copy_package_asset_sha';
@@ -119,6 +111,14 @@ export const DELETE_PACKAGE_VERSIONS_TRACKING_ACTION = 'delete_package_versions'
export const REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION = 'request_delete_package_versions';
export const CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION = 'cancel_delete_package_versions';
+export const DELETE_PACKAGE_VERSION_TRACKING_ACTION = 'delete_package_version';
+export const REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION = 'request_delete_package_version';
+export const CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION = 'cancel_delete_package_version';
+
+export const FETCH_PACKAGE_VERSIONS_ERROR_MESSAGE = s__(
+ 'PackageRegistry|Failed to load version data',
+);
+
export const DELETE_PACKAGES_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while deleting packages.',
);
@@ -126,7 +126,23 @@ export const DELETE_PACKAGES_SUCCESS_MESSAGE = s__('PackageRegistry|Packages del
export const DELETE_PACKAGES_MODAL_TITLE = s__('PackageRegistry|Delete packages');
export const DELETE_PACKAGE_MODAL_PRIMARY_ACTION = s__('PackageRegistry|Permanently delete');
+export const DELETE_PACKAGES_MODAL_DESCRIPTION = s__(
+ 'PackageRegistry|You are about to delete %{count} packages. This operation is irreversible.',
+);
+export const DELETE_PACKAGE_WITH_REQUEST_FORWARDING_PRIMARY_ACTION = s__(
+ 'PackageRegistry|Yes, delete package',
+);
+export const DELETE_PACKAGES_WITH_REQUEST_FORWARDING_PRIMARY_ACTION = s__(
+ 'PackageRegistry|Yes, delete selected packages',
+);
+export const DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT = s__(
+ 'PackageRegistry|Deleting this package while request forwarding is enabled for the project can pose a security risk. Do you want to delete %{name} version %{version} anyway? %{docLinkStart}What are the risks?%{docLinkEnd}',
+);
+export const DELETE_PACKAGES_REQUEST_FORWARDING_MODAL_CONTENT = s__(
+ 'PackageRegistry|Some of the selected package formats allow request forwarding. Deleting a package while request forwarding is enabled for the project can pose a security risk. Do you want to proceed with deleting the selected packages? %{docLinkStart}What are the risks?%{docLinkEnd}',
+);
+export const DELETE_PACKAGE_TEXT = s__('PackageRegistry|Delete package');
export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully');
export const DELETE_PACKAGE_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while deleting the package.',
@@ -142,8 +158,6 @@ export const PACKAGE_REGISTRY_TITLE = __('Package Registry');
export const PACKAGE_ERROR_STATUS = 'ERROR';
export const PACKAGE_DEFAULT_STATUS = 'DEFAULT';
-export const PACKAGE_HIDDEN_STATUS = 'HIDDEN';
-export const PACKAGE_PROCESSING_STATUS = 'PROCESSING';
export const NPM_PACKAGE_MANAGER = 'npm';
export const YARN_PACKAGE_MANAGER = 'yarn';
@@ -151,8 +165,6 @@ export const YARN_PACKAGE_MANAGER = 'yarn';
export const PROJECT_PACKAGE_ENDPOINT_TYPE = 'project';
export const INSTANCE_PACKAGE_ENDPOINT_TYPE = 'instance';
-export const PROJECT_RESOURCE_TYPE = 'project';
-export const GROUP_RESOURCE_TYPE = 'group';
export const GRAPHQL_PAGE_SIZE = 20;
export const LIST_KEY_NAME = 'name';
@@ -214,5 +226,9 @@ export const NUGET_HELP_PATH = helpPagePath('user/packages/nuget_repository/inde
export const PYPI_HELP_PATH = helpPagePath('user/packages/pypi_repository/index');
export const COMPOSER_HELP_PATH = helpPagePath('user/packages/composer_repository/index');
export const PERSONAL_ACCESS_TOKEN_HELP_URL = helpPagePath('user/profile/personal_access_tokens');
+export const REQUEST_FORWARDING_HELP_PAGE_PATH = helpPagePath(
+ 'user/packages/package_registry/supported_functionality',
+ { anchor: 'deleting-packages' },
+);
export const GRAPHQL_PACKAGE_PIPELINES_PAGE_SIZE = 10;
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
index 2d405f3e9cc..bcd90b7bee5 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
@@ -12,7 +12,7 @@ fragment PackageData on Package {
name
}
}
- pipelines(last: 1) {
+ pipelines(first: 1) {
nodes {
id
sha
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql
new file mode 100644
index 00000000000..db05f497b7f
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql
@@ -0,0 +1,8 @@
+fragment GroupPackageSettings on Group {
+ id
+ packageSettings {
+ mavenPackageRequestsForwarding
+ npmPackageRequestsForwarding
+ pypiPackageRequestsForwarding
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
index 56f95fa2c1f..39e5da54509 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
@@ -4,6 +4,27 @@ import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
+export const mergeVariables = (existing, incoming) => {
+ if (!incoming) return existing;
+ return incoming;
+};
+
export const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ typePolicies: {
+ PackageDetailsType: {
+ fields: {
+ versions: {
+ keyArgs: false,
+ merge: mergeVariables,
+ },
+ },
+ },
+ },
+ },
+ },
+ ),
});
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
index 109d535469b..984996b829a 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -1,10 +1,6 @@
-query getPackageDetails(
- $id: PackagesPackageID!
- $first: Int
- $last: Int
- $after: String
- $before: String
-) {
+#import "~/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql"
+
+query getPackageDetails($id: PackagesPackageID!) {
package(id: $id) {
id
name
@@ -15,6 +11,7 @@ query getPackageDetails(
updatedAt
status
canDestroy
+ publicPackage
npmUrl
mavenUrl
conanUrl
@@ -28,6 +25,9 @@ query getPackageDetails(
path
name
fullPath
+ group {
+ ...GroupPackageSettings
+ }
}
tags(first: 10) {
nodes {
@@ -61,31 +61,8 @@ query getPackageDetails(
downloadPath
}
}
- versions(after: $after, before: $before, first: $first, last: $last) {
+ versions {
count
- nodes {
- id
- name
- canDestroy
- createdAt
- version
- status
- _links {
- webPath
- }
- tags(first: 1) {
- nodes {
- id
- name
- }
- }
- }
- pageInfo {
- hasNextPage
- hasPreviousPage
- endCursor
- startCursor
- }
}
dependencyLinks {
nodes {
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_versions.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_versions.query.graphql
new file mode 100644
index 00000000000..a4119ac5821
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_versions.query.graphql
@@ -0,0 +1,38 @@
+query getPackageVersions(
+ $id: PackagesPackageID!
+ $first: Int
+ $last: Int
+ $after: String
+ $before: String
+) {
+ package(id: $id) {
+ id
+ versions(after: $after, before: $before, first: $first, last: $last) {
+ count
+ nodes {
+ id
+ name
+ canDestroy
+ createdAt
+ packageType
+ version
+ status
+ _links {
+ webPath
+ }
+ tags(first: 1) {
+ nodes {
+ id
+ name
+ }
+ }
+ }
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ endCursor
+ startCursor
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
index 5bde5f08e56..f25f24cbc5f 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
@@ -1,4 +1,5 @@
#import "~/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql"
+#import "~/packages_and_registries/package_registry/graphql/fragments/package_group_settings.fragment.graphql"
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
query getPackages(
@@ -32,6 +33,9 @@ query getPackages(
...PageInfo
}
}
+ group {
+ ...GroupPackageSettings
+ }
}
group(fullPath: $fullPath) @include(if: $isGroupPage) {
id
@@ -52,5 +56,6 @@ query getPackages(
...PageInfo
}
}
+ ...GroupPackageSettings
}
}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/index.js b/app/assets/javascripts/packages_and_registries/package_registry/index.js
index 15ed98122a0..e2f8d239bae 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/index.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/index.js
@@ -19,6 +19,7 @@ export default () => {
npmInstanceUrl,
projectListUrl,
groupListUrl,
+ settingsPath,
} = el.dataset;
const isGroupPage = pageType === 'groups';
@@ -48,6 +49,7 @@ export default () => {
projectListUrl,
groupListUrl,
breadCrumbState,
+ settingsPath,
},
render(createElement) {
return createElement(PackageRegistry);
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
index 4591c2eca87..6d4979ac785 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
@@ -2,6 +2,7 @@
import {
GlBadge,
GlButton,
+ GlLink,
GlModal,
GlModalDirective,
GlTooltipDirective,
@@ -10,7 +11,7 @@ import {
GlTabs,
GlSprintf,
} from '@gitlab/ui';
-import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert';
import { TYPENAME_PACKAGES_PACKAGE } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
@@ -37,6 +38,7 @@ import {
DELETE_PACKAGE_FILE_TRACKING_ACTION,
DELETE_PACKAGE_FILES_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
SHOW_DELETE_SUCCESS_ALERT,
FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
@@ -44,6 +46,7 @@ import {
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
DELETE_PACKAGE_FILES_ERROR_MESSAGE,
DELETE_PACKAGE_FILES_SUCCESS_MESSAGE,
+ DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT,
DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION,
DELETE_MODAL_TITLE,
DELETE_MODAL_CONTENT,
@@ -54,6 +57,7 @@ import {
import destroyPackageFilesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_files.mutation.graphql';
import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
+import getPackageVersionsQuery from '~/packages_and_registries/package_registry/graphql/queries/get_package_versions.query.graphql';
import Tracking from '~/tracking';
export default {
@@ -63,6 +67,7 @@ export default {
GlButton,
GlEmptyState,
GlModal,
+ GlLink,
GlTab,
GlTabs,
GlSprintf,
@@ -123,6 +128,11 @@ export default {
},
},
computed: {
+ deleteModalContent() {
+ return this.isRequestForwardingEnabled
+ ? DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT
+ : this.deletePackageModalContent;
+ },
projectName() {
return this.packageEntity.project?.name;
},
@@ -135,7 +145,6 @@ export default {
queryVariables() {
return {
id: convertToGraphQLId(TYPENAME_PACKAGES_PACKAGE, this.packageId),
- first: GRAPHQL_PAGE_SIZE,
};
},
packageFiles() {
@@ -147,9 +156,6 @@ export default {
isLoading() {
return this.$apollo.queries.packageEntity.loading;
},
- isVersionsLoading() {
- return this.isLoading || this.versionsMutationLoading;
- },
packageFilesLoading() {
return this.isLoading || this.mutationLoading;
},
@@ -161,18 +167,24 @@ export default {
category: packageTypeToTrackCategory(this.packageType),
};
},
- versionPageInfo() {
- return this.packageEntity?.versions?.pageInfo ?? {};
- },
packageDependencies() {
return this.packageEntity.dependencyLinks?.nodes || [];
},
+ packageVersionsCount() {
+ return this.packageEntity.versions?.count ?? 0;
+ },
showDependencies() {
return this.packageType === PACKAGE_TYPE_NUGET;
},
showFiles() {
return this.packageType !== PACKAGE_TYPE_COMPOSER;
},
+ groupSettings() {
+ return this.packageEntity.project?.group?.packageSettings ?? {};
+ },
+ isRequestForwardingEnabled() {
+ return this.groupSettings[`${this.packageType.toLowerCase()}PackageRequestsForwarding`];
+ },
showMetadata() {
return [
PACKAGE_TYPE_COMPOSER,
@@ -190,6 +202,17 @@ export default {
},
];
},
+ refetchVersionsQueryData() {
+ return [
+ {
+ query: getPackageVersionsQuery,
+ variables: {
+ id: this.queryVariables.id,
+ first: GRAPHQL_PAGE_SIZE,
+ },
+ },
+ ];
+ },
},
methods: {
formatSize(size) {
@@ -274,34 +297,6 @@ export default {
resetDeleteModalContent() {
this.deletePackageModalContent = DELETE_MODAL_CONTENT;
},
- updateQuery(_, { fetchMoreResult }) {
- return fetchMoreResult;
- },
- fetchPreviousVersionsPage() {
- const variables = {
- ...this.queryVariables,
- first: null,
- last: GRAPHQL_PAGE_SIZE,
- before: this.versionPageInfo?.startCursor,
- };
- this.$apollo.queries.packageEntity.fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
- },
- fetchNextVersionsPage() {
- const variables = {
- ...this.queryVariables,
- first: GRAPHQL_PAGE_SIZE,
- last: null,
- after: this.versionPageInfo?.endCursor,
- };
-
- this.$apollo.queries.packageEntity.fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
- },
},
i18n: {
DELETE_MODAL_TITLE,
@@ -311,22 +306,25 @@ export default {
),
otherVersionsTabTitle: s__('PackageRegistry|Other versions'),
},
+ links: {
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
+ },
modal: {
packageDeletePrimaryAction: {
text: s__('PackageRegistry|Permanently delete'),
- attributes: [
- { variant: 'danger' },
- { category: 'primary' },
- { 'data-qa-selector': 'delete_modal_button' },
- ],
+ attributes: {
+ variant: 'danger',
+ category: 'primary',
+ 'data-qa-selector': 'delete_modal_button',
+ },
},
fileDeletePrimaryAction: {
text: __('Delete'),
- attributes: [{ variant: 'danger' }, { category: 'primary' }],
+ attributes: { variant: 'danger', category: 'primary' },
},
filesDeletePrimaryAction: {
text: s__('PackageRegistry|Permanently delete assets'),
- attributes: [{ variant: 'danger' }, { category: 'primary' }],
+ attributes: { variant: 'danger', category: 'primary' },
},
cancelAction: {
text: __('Cancel'),
@@ -403,12 +401,12 @@ export default {
<template #title>
<span>{{ $options.i18n.otherVersionsTabTitle }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge" data-testid="other-versions-badge">{{
- packageEntity.versions.count
+ packageVersionsCount
}}</gl-badge>
</template>
<delete-packages
- :refetch-queries="refetchQueriesData"
+ :refetch-queries="refetchVersionsQueryData"
show-success-alert
@start="versionsMutationLoading = true"
@end="versionsMutationLoading = false"
@@ -416,12 +414,11 @@ export default {
<template #default="{ deletePackages }">
<package-versions-list
:can-destroy="packageEntity.canDestroy"
- :is-loading="isVersionsLoading"
- :page-info="versionPageInfo"
- :versions="packageEntity.versions.nodes"
+ :count="packageVersionsCount"
+ :is-mutation-loading="versionsMutationLoading"
+ :is-request-forwarding-enabled="isRequestForwardingEnabled"
+ :package-id="packageEntity.id"
@delete="deletePackages"
- @prev-page="fetchPreviousVersionsPage"
- @next-page="fetchNextVersionsPage"
>
<template #empty-state>
<p class="gl-mt-3" data-testid="no-versions-message">
@@ -451,15 +448,23 @@ export default {
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)"
>
<template #modal-title>{{ $options.i18n.DELETE_MODAL_TITLE }}</template>
- <gl-sprintf :message="deletePackageModalContent">
- <template #version>
- <strong>{{ packageEntity.version }}</strong>
- </template>
+ <p>
+ <gl-sprintf :message="deleteModalContent">
+ <template v-if="isRequestForwardingEnabled" #docLink="{ content }">
+ <gl-link :href="$options.links.REQUEST_FORWARDING_HELP_PAGE_PATH">{{
+ content
+ }}</gl-link>
+ </template>
- <template #name>
- <strong>{{ packageEntity.name }}</strong>
- </template>
- </gl-sprintf>
+ <template #version>
+ <strong>{{ packageEntity.version }}</strong>
+ </template>
+
+ <template #name>
+ <strong>{{ packageEntity.name }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
</gl-modal>
</template>
</delete-packages>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
index 31c76c95e45..044ce4e6413 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
@@ -1,12 +1,11 @@
<script>
-import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
-import { createAlert, VARIANT_INFO } from '~/flash';
+import { GlButton, GlEmptyState, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import { createAlert, VARIANT_INFO } from '~/alert';
+import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import { historyReplaceState } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages_and_registries/shared/constants';
import {
- PROJECT_RESOURCE_TYPE,
- GROUP_RESOURCE_TYPE,
GRAPHQL_PAGE_SIZE,
DELETE_PACKAGE_SUCCESS_MESSAGE,
EMPTY_LIST_HELP_URL,
@@ -20,6 +19,7 @@ import PackageList from '~/packages_and_registries/package_registry/components/l
export default {
components: {
+ GlButton,
GlEmptyState,
GlLink,
GlSprintf,
@@ -28,23 +28,26 @@ export default {
PackageSearch,
DeletePackages,
},
- inject: ['emptyListIllustration', 'isGroupPage', 'fullPath'],
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: ['emptyListIllustration', 'isGroupPage', 'fullPath', 'settingsPath'],
data() {
return {
- packages: {},
+ packagesResource: {},
sort: '',
filters: {},
mutationLoading: false,
};
},
apollo: {
- packages: {
+ packagesResource: {
query: getPackagesQuery,
variables() {
return this.queryVariables;
},
update(data) {
- return data[this.graphqlResource].packages;
+ return data[this.graphqlResource] ?? {};
},
skip() {
return !this.sort;
@@ -52,6 +55,14 @@ export default {
},
},
computed: {
+ packages() {
+ return this.packagesResource?.packages ?? {};
+ },
+ groupSettings() {
+ return this.isGroupPage
+ ? this.packagesResource?.packageSettings ?? {}
+ : this.packagesResource?.group?.packageSettings ?? {};
+ },
queryVariables() {
return {
isGroupPage: this.isGroupPage,
@@ -64,7 +75,7 @@ export default {
};
},
graphqlResource() {
- return this.isGroupPage ? GROUP_RESOURCE_TYPE : PROJECT_RESOURCE_TYPE;
+ return this.isGroupPage ? WORKSPACE_GROUP : WORKSPACE_PROJECT;
},
pageInfo() {
return this.packages?.pageInfo ?? {};
@@ -84,7 +95,7 @@ export default {
: this.$options.i18n.noResultsTitle;
},
isLoading() {
- return this.$apollo.queries.packages.loading || this.mutationLoading;
+ return this.$apollo.queries.packagesResource.loading || this.mutationLoading;
},
refetchQueriesData() {
return [
@@ -124,7 +135,7 @@ export default {
after: this.pageInfo?.endCursor,
};
- this.$apollo.queries.packages.fetchMore({
+ this.$apollo.queries.packagesResource.fetchMore({
variables,
updateQuery: this.updateQuery,
});
@@ -137,7 +148,7 @@ export default {
before: this.pageInfo?.startCursor,
};
- this.$apollo.queries.packages.fetchMore({
+ this.$apollo.queries.packagesResource.fetchMore({
variables,
updateQuery: this.updateQuery,
});
@@ -150,6 +161,7 @@ export default {
noResultsText: s__(
'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.',
),
+ settingsText: s__('PackageRegistry|Configure in settings'),
},
links: {
EMPTY_LIST_HELP_URL,
@@ -160,7 +172,16 @@ export default {
<template>
<div>
- <package-title :help-url="$options.links.PACKAGE_HELP_URL" :count="packagesCount" />
+ <package-title :help-url="$options.links.PACKAGE_HELP_URL" :count="packagesCount">
+ <template v-if="settingsPath" #settings-link>
+ <gl-button
+ v-gl-tooltip="$options.i18n.settingsText"
+ icon="settings"
+ :href="settingsPath"
+ :aria-label="$options.i18n.settingsText"
+ />
+ </template>
+ </package-title>
<package-search class="gl-mb-5" @update="handleSearchUpdate" />
<delete-packages
@@ -171,6 +192,7 @@ export default {
>
<template #default="{ deletePackages }">
<package-list
+ :group-settings="groupSettings"
:list="packages.nodes"
:is-loading="isLoading"
:page-info="pageInfo"
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue
index 36eb65c623b..4c25c0f97de 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue
@@ -72,7 +72,7 @@ export default {
</script>
<template>
- <div>
+ <div data-testid="packages-and-registries-group-settings">
<gl-alert v-if="alertMessage" variant="warning" class="gl-mt-4" @dismiss="dismissAlert">
{{ alertMessage }}
</gl-alert>
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/packages_forwarding_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/packages_forwarding_settings.vue
index b7d7f0aaca7..ab88d9e8936 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/packages_forwarding_settings.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/packages_forwarding_settings.vue
@@ -1,12 +1,14 @@
<script>
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlLink, GlSprintf } from '@gitlab/ui';
import { isEqual } from 'lodash';
import {
+ PACKAGE_FORWARDING_SECURITY_DESCRIPTION,
PACKAGE_FORWARDING_SETTINGS_HEADER,
PACKAGE_FORWARDING_SETTINGS_DESCRIPTION,
PACKAGE_FORWARDING_FORM_BUTTON,
PACKAGE_FORWARDING_FIELDS,
MAVEN_FORWARDING_FIELDS,
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
} from '~/packages_and_registries/settings/group/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql';
@@ -20,12 +22,15 @@ export default {
name: 'PackageForwardingSettings',
i18n: {
PACKAGE_FORWARDING_FORM_BUTTON,
+ PACKAGE_FORWARDING_SECURITY_DESCRIPTION,
PACKAGE_FORWARDING_SETTINGS_HEADER,
PACKAGE_FORWARDING_SETTINGS_DESCRIPTION,
},
components: {
ForwardingSettings,
GlButton,
+ GlLink,
+ GlSprintf,
SettingsBlock,
},
mixins: [glFeatureFlagsMixin()],
@@ -150,6 +155,9 @@ export default {
this.$set(this.workingCopy, type, value);
},
},
+ links: {
+ REQUEST_FORWARDING_HELP_PAGE_PATH,
+ },
};
</script>
@@ -157,9 +165,14 @@ export default {
<settings-block>
<template #title> {{ $options.i18n.PACKAGE_FORWARDING_SETTINGS_HEADER }}</template>
<template #description>
- <span data-testid="description">
+ <span class="gl-display-block gl-mb-2" data-testid="description">
{{ $options.i18n.PACKAGE_FORWARDING_SETTINGS_DESCRIPTION }}
</span>
+ <gl-sprintf :message="$options.i18n.PACKAGE_FORWARDING_SECURITY_DESCRIPTION">
+ <template #docLink="{ content }">
+ <gl-link :href="$options.links.REQUEST_FORWARDING_HELP_PAGE_PATH">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
</template>
<template #default>
<form @submit.prevent="submit">
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/constants.js b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
index c93cd7f7d78..fa73c01c5c4 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
@@ -17,6 +17,9 @@ export const DUPLICATES_SETTINGS_EXCEPTION_LEGEND = s__(
'PackageRegistry|Publish packages if their name or version matches this regex.',
);
+export const PACKAGE_FORWARDING_SECURITY_DESCRIPTION = s__(
+ 'PackageRegistry|There are security risks if packages are deleted while request forwarding is enabled. %{docLinkStart}What are the risks?%{docLinkEnd}',
+);
export const PACKAGE_FORWARDING_SETTINGS_HEADER = s__('PackageRegistry|Package forwarding');
export const PACKAGE_FORWARDING_SETTINGS_DESCRIPTION = s__(
'PackageRegistry|Forward package requests to a public registry if the packages are not found in the GitLab package registry.',
@@ -78,8 +81,8 @@ export const MAVEN_FORWARDING_FIELDS = {
// Parameters
-export const PACKAGES_DOCS_PATH = helpPagePath('user/packages/index');
-export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed';
-export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex';
-
export const DEPENDENCY_PROXY_DOCS_PATH = helpPagePath('user/packages/dependency_proxy/index');
+export const REQUEST_FORWARDING_HELP_PAGE_PATH = helpPagePath(
+ 'user/packages/package_registry/supported_functionality',
+ { anchor: 'deleting-packages' },
+);
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue
index 11d8732426d..c13d49b5379 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue
@@ -225,9 +225,6 @@ export default {
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
- <template #secondStrong="{ content }">
- <strong>{{ content }}</strong>
- </template>
</gl-sprintf>
</p>
<expiration-dropdown
@@ -264,9 +261,6 @@ export default {
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
- <template #secondStrong="{ content }">
- <strong>{{ content }}</strong>
- </template>
</gl-sprintf>
</p>
<expiration-dropdown
@@ -312,7 +306,7 @@ export default {
>
{{ __('Cancel') }}
</gl-button>
- <span class="gl-font-style-italic gl-text-gray-400">{{
+ <span class="gl-font-style-italic gl-text-gray-500">{{
$options.i18n.EXPIRATION_POLICY_FOOTER_NOTE
}}</span>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_dropdown.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_dropdown.vue
index f06e3a41bd0..0bbb501011a 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_dropdown.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_dropdown.vue
@@ -64,7 +64,7 @@ export default {
</gl-form-select>
</div>
<template v-if="description" #description>
- <span data-testid="description" class="gl-text-gray-400">
+ <span data-testid="description" class="gl-text-gray-500">
{{ description }}
</span>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_input.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_input.vue
index 3fbbfd75ffb..749650e1060 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_input.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_input.vue
@@ -101,7 +101,7 @@ export default {
trim
/>
<template #description>
- <span data-testid="description" class="gl-text-gray-400">
+ <span data-testid="description" class="gl-text-gray-500">
<gl-sprintf :message="description">
<template #link="{ content }">
<gl-link :href="tagsRegexHelpPagePath">{{ content }}</gl-link>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_toggle.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_toggle.vue
index 7a9ea7c0bf7..35fc0910a16 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_toggle.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/expiration_toggle.vue
@@ -8,7 +8,7 @@ import {
export default {
i18n: {
- toggleLabel: s__('ContainerRegistry|Enable expiration policy'),
+ toggleLabel: s__('ContainerRegistry|Enable cleanup policy'),
},
components: {
GlFormGroup,
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
index 2c1368262f2..4cc9cc190e8 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
@@ -42,7 +42,7 @@ export default {
</script>
<template>
- <div>
+ <div data-testid="packages-and-registries-project-settings">
<gl-alert
v-if="showAlert"
variant="success"
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/constants.js b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
index 731fb3e4c45..05616a0a4f6 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
@@ -1,6 +1,6 @@
import { s__, __ } from '~/locale';
-export const CONTAINER_CLEANUP_POLICY_TITLE = s__(`ContainerRegistry|Clean up image tags`);
+export const CONTAINER_CLEANUP_POLICY_TITLE = s__('ContainerRegistry|Cleanup policies');
export const CONTAINER_CLEANUP_POLICY_DESCRIPTION = s__(
`ContainerRegistry|Save storage space by automatically deleting tags from the container registry and keeping the ones you want. %{linkStart}How does cleanup work?%{linkEnd}`,
);
@@ -29,7 +29,7 @@ export const TEXT_AREA_INVALID_FEEDBACK = s__(
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.',
+ 'ContainerRegistry|Tags that match %{strongStart}any of%{strongEnd} these rules are %{strongStart}kept%{strongEnd}, even if they match a removal rule below. The %{strongStart}latest%{strongEnd} 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:');
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue b/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue
deleted file mode 100644
index 105f7bbe132..00000000000
--- a/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-
-export default {
- name: 'PackageIconAndName',
- components: {
- GlIcon,
- },
-};
-</script>
-
-<template>
- <div class="gl-display-flex gl-align-items-center">
- <gl-icon name="package" class="gl-ml-3 gl-mr-2" />
- <span><slot></slot></span>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
index 7485f8282ee..1c8f80972df 100644
--- a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
+++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
@@ -125,7 +125,7 @@ export default {
:select-item="selectItem"
:is-selected="isSelected"
:item="item"
- :first="index === 0"
+ :first="!hiddenDelete && index === 0"
></slot>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/shared/utils.js b/app/assets/javascripts/packages_and_registries/shared/utils.js
index 76623377d90..adffab277cc 100644
--- a/app/assets/javascripts/packages_and_registries/shared/utils.js
+++ b/app/assets/javascripts/packages_and_registries/shared/utils.js
@@ -55,15 +55,6 @@ export const renderBreadcrumb = (router, apolloProvider, RegistryBreadcrumb) =>
RegistryBreadcrumb,
},
render(createElement) {
- // FIXME(@tnir): this is a workaround until the MR gets merged:
- // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115
- const parentEl = breadCrumbEl.parentElement.parentElement;
- if (parentEl) {
- parentEl.classList.remove('breadcrumbs-container');
- parentEl.classList.add('gl-display-flex');
- parentEl.classList.add('w-100');
- }
- // End of FIXME(@tnir)
return createElement('registry-breadcrumb', {
class: breadCrumbEl.className,
props: {