diff options
Diffstat (limited to 'app/assets/javascripts/registry/explorer/pages/details.vue')
-rw-r--r-- | app/assets/javascripts/registry/explorer/pages/details.vue | 401 |
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"> - · - </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> |