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/details.vue')
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue401
1 files changed, 54 insertions, 347 deletions
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index cc2dc531dc8..598e643ce1a 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -1,139 +1,56 @@
<script>
-import { mapState, mapActions, mapGetters } from 'vuex';
-import {
- GlTable,
- GlFormCheckbox,
- GlDeprecatedButton,
- GlIcon,
- GlTooltipDirective,
- GlPagination,
- GlModal,
- GlSprintf,
- GlAlert,
- GlLink,
- GlEmptyState,
- GlResizeObserverDirective,
- GlSkeletonLoader,
-} from '@gitlab/ui';
+import { mapState, mapActions } from 'vuex';
+import { GlPagination, GlResizeObserverDirective } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
-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';
import Tracking from '~/tracking';
+import DeleteAlert from '../components/details_page/delete_alert.vue';
+import DeleteModal from '../components/details_page/delete_modal.vue';
+import DetailsHeader from '../components/details_page/details_header.vue';
+import TagsTable from '../components/details_page/tags_table.vue';
+import TagsLoader from '../components/details_page/tags_loader.vue';
+import EmptyTagsState from '../components/details_page/empty_tags_state.vue';
+
import { decodeAndParse } from '../utils';
import {
- LIST_KEY_TAG,
- LIST_KEY_IMAGE_ID,
- LIST_KEY_SIZE,
- LIST_KEY_LAST_UPDATED,
- LIST_KEY_ACTIONS,
- LIST_KEY_CHECKBOX,
- LIST_LABEL_TAG,
- LIST_LABEL_IMAGE_ID,
- LIST_LABEL_SIZE,
- LIST_LABEL_LAST_UPDATED,
- DELETE_TAG_SUCCESS_MESSAGE,
- 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';
+ ALERT_SUCCESS_TAG,
+ ALERT_DANGER_TAG,
+ ALERT_SUCCESS_TAGS,
+ ALERT_DANGER_TAGS,
+} from '../constants/index';
export default {
components: {
- GlTable,
- GlFormCheckbox,
- GlDeprecatedButton,
- GlIcon,
- ClipboardButton,
+ DeleteAlert,
+ DetailsHeader,
GlPagination,
- GlModal,
- GlSkeletonLoader,
- GlSprintf,
- GlEmptyState,
- GlAlert,
- GlLink,
+ DeleteModal,
+ TagsTable,
+ TagsLoader,
+ EmptyTagsState,
},
directives: {
- GlTooltip: GlTooltipDirective,
GlResizeObserver: GlResizeObserverDirective,
},
- mixins: [timeagoMixin, Tracking.mixin()],
- loader: {
- repeat: 10,
- 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,
- },
+ mixins: [Tracking.mixin()],
data() {
return {
- selectedItems: [],
itemsToBeDeleted: [],
- selectAllChecked: false,
- modalDescription: null,
isDesktop: true,
- deleteAlertType: false,
+ deleteAlertType: null,
};
},
computed: {
- ...mapGetters(['tags']),
- ...mapState(['tagsPagination', 'isLoading', 'config']),
+ ...mapState(['tagsPagination', 'isLoading', 'config', 'tags']),
imageName() {
const { name } = decodeAndParse(this.$route.params.id);
return name;
},
- 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`,
- 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 },
- { key: LIST_KEY_ACTIONS, label: '' },
- ].filter(f => f.key !== LIST_KEY_CHECKBOX || this.isDesktop);
- },
- isMultiDelete() {
- return this.itemsToBeDeleted.length > 1;
- },
tracking() {
return {
- label: this.isMultiDelete ? 'bulk_registry_tag_delete' : 'registry_tag_delete',
+ label:
+ this.itemsToBeDeleted?.length > 1 ? 'bulk_registry_tag_delete' : 'registry_tag_delete',
};
},
- modalAction() {
- return n__(
- 'ContainerRegistry|Remove tag',
- 'ContainerRegistry|Remove tags',
- this.isMultiDelete ? this.itemsToBeDeleted.length : 1,
- );
- },
currentPage: {
get() {
return this.tagsPagination.page;
@@ -142,132 +59,51 @@ 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: REMOVE_TAGS_CONFIRMATION_TEXT,
- item: this.itemsToBeDeleted.length,
- };
- } else {
- const { path } = this.tags[itemIndex];
-
- this.modalDescription = {
- message: REMOVE_TAG_CONFIRMATION_TEXT,
- item: path,
- };
- }
- },
- formatSize(size) {
- return numberToHumanSize(size);
- },
- layers(layers) {
- return layers ? n__('%d layer', '%d layers', layers) : '';
- },
- onSelectAllChange() {
- if (this.selectAllChecked) {
- this.deselectAll();
- } else {
- this.selectAll();
- }
- },
- selectAll() {
- this.selectedItems = this.tags.map((x, index) => index);
- this.selectAllChecked = true;
- },
- deselectAll() {
- this.selectedItems = [];
- this.selectAllChecked = false;
- },
- updateSelectedItems(index) {
- const delIndex = this.selectedItems.findIndex(x => x === index);
-
- if (delIndex > -1) {
- this.selectedItems.splice(delIndex, 1);
- this.selectAllChecked = false;
- } else {
- this.selectedItems.push(index);
-
- if (this.selectedItems.length === this.tags.length) {
- this.selectAllChecked = true;
- }
- }
- },
- deleteSingleItem(index) {
- this.setModalDescription(index);
- this.itemsToBeDeleted = [index];
+ deleteTags(toBeDeletedList) {
+ this.itemsToBeDeleted = toBeDeletedList.map(name => ({
+ ...this.tags.find(t => t.name === name),
+ }));
this.track('click_button');
this.$refs.deleteModal.show();
},
- deleteMultipleItems() {
- this.itemsToBeDeleted = [...this.selectedItems];
- if (this.selectedItems.length === 1) {
- this.setModalDescription(this.itemsToBeDeleted[0]);
- } else if (this.selectedItems.length > 1) {
- this.setModalDescription();
- }
- this.track('click_button');
- this.$refs.deleteModal.show();
- },
- handleSingleDelete(index) {
- const itemToDelete = this.tags[index];
+ handleSingleDelete() {
+ const [itemToDelete] = this.itemsToBeDeleted;
this.itemsToBeDeleted = [];
- this.selectedItems = this.selectedItems.filter(i => i !== index);
return this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id })
.then(() => {
- this.deleteAlertType = 'success_tag';
+ this.deleteAlertType = ALERT_SUCCESS_TAG;
})
.catch(() => {
- this.deleteAlertType = 'danger_tag';
+ this.deleteAlertType = ALERT_DANGER_TAG;
});
},
handleMultipleDelete() {
const { itemsToBeDeleted } = this;
this.itemsToBeDeleted = [];
- this.selectedItems = [];
return this.requestDeleteTags({
- ids: itemsToBeDeleted.map(x => this.tags[x].name),
+ ids: itemsToBeDeleted.map(x => x.name),
params: this.$route.params.id,
})
.then(() => {
- this.deleteAlertType = 'success_tags';
+ this.deleteAlertType = ALERT_SUCCESS_TAGS;
})
.catch(() => {
- this.deleteAlertType = 'danger_tags';
+ this.deleteAlertType = ALERT_DANGER_TAGS;
});
},
onDeletionConfirmed() {
this.track('confirm_delete');
- if (this.isMultiDelete) {
+ if (this.itemsToBeDeleted.length > 1) {
this.handleMultipleDelete();
} else {
- this.handleSingleDelete(this.itemsToBeDeleted[0]);
+ this.handleSingleDelete();
}
},
handleResize() {
@@ -279,141 +115,23 @@ 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"
+ <delete-alert
+ v-model="deleteAlertType"
+ :garbage-collection-help-page-path="config.garbageCollectionHelpPagePath"
+ :is-admin="config.isAdmin"
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="$options.i18n.DETAILS_PAGE_TITLE">
- <template #imageName>
- {{ imageName }}
- </template>
- </gl-sprintf>
- </h4>
- </div>
-
- <gl-table :items="tags" :fields="fields" :stacked="!isDesktop" show-empty>
- <template v-if="isDesktop" #head(checkbox)>
- <gl-form-checkbox
- ref="mainCheckbox"
- :checked="selectAllChecked"
- @change="onSelectAllChange"
- />
- </template>
- <template #head(actions)>
- <gl-deprecated-button
- ref="bulkDeleteButton"
- v-gl-tooltip
- :disabled="!selectedItems || selectedItems.length === 0"
- class="float-right"
- variant="danger"
- :title="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
- :aria-label="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
- @click="deleteMultipleItems()"
- >
- <gl-icon name="remove" />
- </gl-deprecated-button>
- </template>
+ />
- <template #cell(checkbox)="{index}">
- <gl-form-checkbox
- ref="rowCheckbox"
- class="js-row-checkbox"
- :checked="selectedItems.includes(index)"
- @change="updateSelectedItems(index)"
- />
- </template>
- <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">
- {{ value }}
- </span>
- </template>
- <template #cell(total_size)="{item}">
- <span ref="rowSize">
- {{ formatSize(item.total_size) }}
- <template v-if="item.total_size && item.layers">
- &middot;
- </template>
- {{ layers(item.layers) }}
- </span>
- </template>
- <template #cell(created_at)="{value}">
- <span ref="rowTime" v-gl-tooltip :title="tooltipTitle(value)">
- {{ timeFormatted(value) }}
- </span>
- </template>
- <template #cell(actions)="{index, item}">
- <gl-deprecated-button
- ref="singleDeleteButton"
- :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"
- @click="deleteSingleItem(index)"
- >
- <gl-icon name="remove" />
- </gl-deprecated-button>
- </template>
+ <details-header :image-name="imageName" />
+ <tags-table :tags="tags" :is-loading="isLoading" :is-desktop="isDesktop" @delete="deleteTags">
<template #empty>
- <template v-if="isLoading">
- <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="15" x="0" y="12.5" height="15" rx="4" />
- <rect width="250" x="25" y="10" height="20" rx="4" />
- <circle cx="290" cy="20" r="10" />
- <rect width="100" x="315" y="10" height="20" rx="4" />
- <rect width="100" x="500" y="10" height="20" rx="4" />
- <rect width="100" x="630" y="10" height="20" rx="4" />
- <rect x="960" y="0" width="40" height="40" rx="4" />
- </gl-skeleton-loader>
- </template>
- <gl-empty-state
- v-else
- :title="$options.i18n.EMPTY_IMAGE_REPOSITORY_TITLE"
- :svg-path="config.noContainersImage"
- :description="$options.i18n.EMPTY_IMAGE_REPOSITORY_MESSAGE"
- class="mx-auto my-0"
- />
+ <empty-tags-state :no-containers-image="config.noContainersImage" />
</template>
- </gl-table>
+ <template #loader>
+ <tags-loader v-once />
+ </template>
+ </tags-table>
<gl-pagination
v-if="!isLoading"
@@ -425,22 +143,11 @@ export default {
class="w-100"
/>
- <gl-modal
+ <delete-modal
ref="deleteModal"
- modal-id="delete-tag-modal"
- ok-variant="danger"
- @ok="onDeletionConfirmed"
+ :items-to-be-deleted="itemsToBeDeleted"
+ @confirmDelete="onDeletionConfirmed"
@cancel="track('cancel_delete')"
- >
- <template #modal-title>{{ modalAction }}</template>
- <template #modal-ok>{{ modalAction }}</template>
- <p v-if="modalDescription">
- <gl-sprintf :message="modalDescription.message">
- <template #item>
- <b>{{ modalDescription.item }}</b>
- </template>
- </gl-sprintf>
- </p>
- </gl-modal>
+ />
</div>
</template>