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