debian-mirror-gitlab/app/assets/javascripts/registry/explorer/pages/list.vue

380 lines
11 KiB
Vue
Raw Normal View History

2020-03-13 15:44:24 +05:30
<script>
import {
GlEmptyState,
GlTooltipDirective,
GlModal,
GlSprintf,
GlLink,
2020-04-22 19:07:51 +05:30
GlAlert,
2020-04-08 14:13:33 +05:30
GlSkeletonLoader,
2020-03-13 15:44:24 +05:30
} from '@gitlab/ui';
2021-03-08 18:12:59 +05:30
import { get } from 'lodash';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
2021-02-22 17:27:13 +05:30
import createFlash from '~/flash';
2021-03-11 19:13:27 +05:30
import Tracking from '~/tracking';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import DeleteImage from '../components/delete_image.vue';
2020-06-23 00:09:42 +05:30
import RegistryHeader from '../components/list_page/registry_header.vue';
2020-05-24 23:13:21 +05:30
2020-04-22 19:07:51 +05:30
import {
DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
CONNECTION_ERROR_TITLE,
CONNECTION_ERROR_MESSAGE,
REMOVE_REPOSITORY_MODAL_TEXT,
2020-05-24 23:13:21 +05:30
REMOVE_REPOSITORY_LABEL,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
2021-02-22 17:27:13 +05:30
GRAPHQL_PAGE_SIZE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
2021-03-11 19:13:27 +05:30
SORT_FIELDS,
2020-06-23 00:09:42 +05:30
} from '../constants/index';
2021-03-11 19:13:27 +05:30
import getContainerRepositoriesDetails from '../graphql/queries/get_container_repositories_details.query.graphql';
2020-03-13 15:44:24 +05:30
export default {
2021-03-08 18:12:59 +05:30
name: 'RegistryListPage',
2020-03-13 15:44:24 +05:30
components: {
GlEmptyState,
2021-03-08 18:12:59 +05:30
ProjectEmptyState: () =>
import(
/* webpackChunkName: 'container_registry_components' */ '../components/list_page/project_empty_state.vue'
),
GroupEmptyState: () =>
import(
/* webpackChunkName: 'container_registry_components' */ '../components/list_page/group_empty_state.vue'
),
ImageList: () =>
import(
/* webpackChunkName: 'container_registry_components' */ '../components/list_page/image_list.vue'
),
CliCommands: () =>
import(
/* webpackChunkName: 'container_registry_components' */ '../components/list_page/cli_commands.vue'
),
2020-03-13 15:44:24 +05:30
GlModal,
GlSprintf,
GlLink,
2020-04-22 19:07:51 +05:30
GlAlert,
2020-04-08 14:13:33 +05:30
GlSkeletonLoader,
2020-06-23 00:09:42 +05:30
RegistryHeader,
2021-03-11 19:13:27 +05:30
DeleteImage,
RegistrySearch,
2020-03-13 15:44:24 +05:30
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
2021-03-08 18:12:59 +05:30
inject: ['config'],
2020-04-08 14:13:33 +05:30
loader: {
repeat: 10,
width: 1000,
height: 40,
},
2020-04-22 19:07:51 +05:30
i18n: {
2020-05-24 23:13:21 +05:30
CONNECTION_ERROR_TITLE,
CONNECTION_ERROR_MESSAGE,
REMOVE_REPOSITORY_MODAL_TEXT,
REMOVE_REPOSITORY_LABEL,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
2020-04-22 19:07:51 +05:30
},
2021-03-11 19:13:27 +05:30
searchConfig: SORT_FIELDS,
2021-02-22 17:27:13 +05:30
apollo: {
2021-03-08 18:12:59 +05:30
baseImages: {
query: getContainerRepositoriesQuery,
2021-02-22 17:27:13 +05:30
variables() {
return this.queryVariables;
},
update(data) {
return data[this.graphqlResource]?.containerRepositories.nodes;
},
result({ data }) {
this.pageInfo = data[this.graphqlResource]?.containerRepositories?.pageInfo;
this.containerRepositoriesCount = data[this.graphqlResource]?.containerRepositoriesCount;
},
error() {
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
},
},
2021-03-08 18:12:59 +05:30
additionalDetails: {
skip() {
return !this.fetchAdditionalDetails;
},
query: getContainerRepositoriesDetails,
variables() {
return this.queryVariables;
},
update(data) {
return data[this.graphqlResource]?.containerRepositories.nodes;
},
error() {
createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE });
},
},
2021-02-22 17:27:13 +05:30
},
2020-03-13 15:44:24 +05:30
data() {
return {
2021-03-08 18:12:59 +05:30
baseImages: [],
additionalDetails: [],
2021-02-22 17:27:13 +05:30
pageInfo: {},
containerRepositoriesCount: 0,
2020-03-13 15:44:24 +05:30
itemToDelete: {},
2020-04-22 19:07:51 +05:30
deleteAlertType: null,
2021-03-11 19:13:27 +05:30
filter: [],
sorting: { orderBy: 'UPDATED', sort: 'desc' },
2021-02-22 17:27:13 +05:30
name: null,
mutationLoading: false,
2021-03-08 18:12:59 +05:30
fetchAdditionalDetails: false,
2020-03-13 15:44:24 +05:30
};
},
computed: {
2021-03-08 18:12:59 +05:30
images() {
return this.baseImages.map((image, index) => ({
...image,
...get(this.additionalDetails, index, {}),
}));
},
2021-02-22 17:27:13 +05:30
graphqlResource() {
return this.config.isGroupPage ? 'group' : 'project';
},
queryVariables() {
return {
name: this.name,
2021-03-11 19:13:27 +05:30
sort: this.sortBy,
2021-02-22 17:27:13 +05:30
fullPath: this.config.isGroupPage ? this.config.groupPath : this.config.projectPath,
2021-03-08 18:12:59 +05:30
isGroupPage: this.config.isGroupPage,
2021-02-22 17:27:13 +05:30
first: GRAPHQL_PAGE_SIZE,
};
},
2020-03-13 15:44:24 +05:30
tracking() {
return {
label: 'registry_repository_delete',
};
},
2021-02-22 17:27:13 +05:30
isLoading() {
2021-03-08 18:12:59 +05:30
return this.$apollo.queries.baseImages.loading || this.mutationLoading;
2021-02-22 17:27:13 +05:30
},
2020-06-23 00:09:42 +05:30
showCommands() {
2020-04-22 19:07:51 +05:30
return Boolean(!this.isLoading && !this.config?.isGroupPage && this.images?.length);
},
showDeleteAlert() {
return this.deleteAlertType && this.itemToDelete?.path;
},
deleteImageAlertMessage() {
return this.deleteAlertType === 'success'
? DELETE_IMAGE_SUCCESS_MESSAGE
: DELETE_IMAGE_ERROR_MESSAGE;
},
2021-03-11 19:13:27 +05:30
sortBy() {
const { orderBy, sort } = this.sorting;
return `${orderBy}_${sort}`.toUpperCase();
},
2020-03-13 15:44:24 +05:30
},
2021-03-08 18:12:59 +05:30
mounted() {
// If the two graphql calls - which are not batched - resolve togheter we will have a race
// condition when apollo sets the cache, with this we give the 'base' call an headstart
setTimeout(() => {
this.fetchAdditionalDetails = true;
}, 200);
},
2020-03-13 15:44:24 +05:30
methods: {
deleteImage(item) {
this.track('click_button');
this.itemToDelete = item;
this.$refs.deleteModal.show();
},
2020-04-22 19:07:51 +05:30
dismissDeleteAlert() {
this.deleteAlertType = null;
this.itemToDelete = {};
},
2021-03-08 18:12:59 +05:30
updateQuery(_, { fetchMoreResult }) {
return fetchMoreResult;
},
async fetchNextPage() {
2021-02-22 17:27:13 +05:30
if (this.pageInfo?.hasNextPage) {
2021-03-08 18:12:59 +05:30
const variables = {
after: this.pageInfo?.endCursor,
first: GRAPHQL_PAGE_SIZE,
};
this.$apollo.queries.baseImages.fetchMore({
variables,
updateQuery: this.updateQuery,
});
await this.$nextTick();
this.$apollo.queries.additionalDetails.fetchMore({
variables,
updateQuery: this.updateQuery,
2021-02-22 17:27:13 +05:30
});
}
},
2021-03-08 18:12:59 +05:30
async fetchPreviousPage() {
2021-02-22 17:27:13 +05:30
if (this.pageInfo?.hasPreviousPage) {
2021-03-08 18:12:59 +05:30
const variables = {
first: null,
before: this.pageInfo?.startCursor,
last: GRAPHQL_PAGE_SIZE,
};
this.$apollo.queries.baseImages.fetchMore({
variables,
updateQuery: this.updateQuery,
});
await this.$nextTick();
this.$apollo.queries.additionalDetails.fetchMore({
variables,
updateQuery: this.updateQuery,
2021-02-22 17:27:13 +05:30
});
}
},
2021-03-11 19:13:27 +05:30
startDelete() {
this.track('confirm_delete');
this.mutationLoading = true;
},
updateSorting(value) {
this.sorting = {
...this.sorting,
...value,
};
},
doFilter() {
const search = this.filter.find((i) => i.type === 'filtered-search-term');
this.name = search?.value?.data;
},
2020-03-13 15:44:24 +05:30
},
};
</script>
<template>
2020-10-24 23:57:45 +05:30
<div>
2020-04-22 19:07:51 +05:30
<gl-alert
v-if="showDeleteAlert"
:variant="deleteAlertType"
2021-02-22 17:27:13 +05:30
class="gl-mt-5"
2020-04-22 19:07:51 +05:30
dismissible
@dismiss="dismissDeleteAlert"
>
<gl-sprintf :message="deleteImageAlertMessage">
<template #title>
{{ itemToDelete.path }}
</template>
</gl-sprintf>
</gl-alert>
2020-03-13 15:44:24 +05:30
<gl-empty-state
v-if="config.characterError"
2020-05-24 23:13:21 +05:30
:title="$options.i18n.CONNECTION_ERROR_TITLE"
2020-03-13 15:44:24 +05:30
:svg-path="config.containersErrorImage"
>
<template #description>
<p>
2020-05-24 23:13:21 +05:30
<gl-sprintf :message="$options.i18n.CONNECTION_ERROR_MESSAGE">
2021-03-08 18:12:59 +05:30
<template #docLink="{ content }">
2020-03-13 15:44:24 +05:30
<gl-link :href="`${config.helpPagePath}#docker-connection-error`" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</p>
</template>
</gl-empty-state>
<template v-else>
2020-06-23 00:09:42 +05:30
<registry-header
2021-03-08 18:12:59 +05:30
:metadata-loading="isLoading"
2021-02-22 17:27:13 +05:30
:images-count="containerRepositoriesCount"
2020-06-23 00:09:42 +05:30
:expiration-policy="config.expirationPolicy"
:help-page-path="config.helpPagePath"
:expiration-policy-help-page-path="config.expirationPolicyHelpPagePath"
:hide-expiration-policy-data="config.isGroupPage"
>
<template #commands>
<cli-commands v-if="showCommands" />
</template>
</registry-header>
2020-03-13 15:44:24 +05:30
2021-03-11 19:13:27 +05:30
<registry-search
:filter="filter"
:sorting="sorting"
:tokens="[]"
:sortable-fields="$options.searchConfig"
@sorting:changed="updateSorting"
@filter:changed="filter = $event"
@filter:submit="doFilter"
/>
2021-02-22 17:27:13 +05:30
<div v-if="isLoading" class="gl-mt-5">
2020-04-08 14:13:33 +05:30
<gl-skeleton-loader
v-for="index in $options.loader.repeat"
:key="index"
:width="$options.loader.width"
:height="$options.loader.height"
preserve-aspect-ratio="xMinYMax meet"
>
<rect width="500" x="10" y="10" height="20" rx="4" />
<circle cx="525" cy="20" r="10" />
<rect x="960" y="0" width="40" height="40" rx="4" />
</gl-skeleton-loader>
</div>
<template v-else>
2021-02-22 17:27:13 +05:30
<template v-if="images.length > 0 || name">
2020-05-24 23:13:21 +05:30
<image-list
v-if="images.length"
:images="images"
2021-03-08 18:12:59 +05:30
:metadata-loading="$apollo.queries.additionalDetails.loading"
2021-02-22 17:27:13 +05:30
:page-info="pageInfo"
2020-05-24 23:13:21 +05:30
@delete="deleteImage"
2021-02-22 17:27:13 +05:30
@prev-page="fetchPreviousPage"
@next-page="fetchNextPage"
2020-03-13 15:44:24 +05:30
/>
2020-04-08 14:13:33 +05:30
2020-05-24 23:13:21 +05:30
<gl-empty-state
v-else
:svg-path="config.noContainersImage"
data-testid="emptySearch"
:title="$options.i18n.EMPTY_RESULT_TITLE"
>
<template #description>
{{ $options.i18n.EMPTY_RESULT_MESSAGE }}
</template>
</gl-empty-state>
</template>
2020-03-13 15:44:24 +05:30
<template v-else>
<project-empty-state v-if="!config.isGroupPage" />
<group-empty-state v-else />
</template>
</template>
2021-03-11 19:13:27 +05:30
<delete-image
:id="itemToDelete.id"
@start="startDelete"
@error="deleteAlertType = 'danger'"
@success="deleteAlertType = 'success'"
@end="mutationLoading = false"
2020-03-13 15:44:24 +05:30
>
2021-03-11 19:13:27 +05:30
<template #default="{ doDelete }">
<gl-modal
ref="deleteModal"
modal-id="delete-image-modal"
:action-primary="{ text: __('Remove'), attributes: { variant: 'danger' } }"
@primary="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>
2020-03-13 15:44:24 +05:30
</template>
</div>
</template>