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/components/details_page')
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/details_row.vue26
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue77
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue220
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue210
4 files changed, 323 insertions, 210 deletions
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/details_row.vue b/app/assets/javascripts/registry/explorer/components/details_page/details_row.vue
new file mode 100644
index 00000000000..c4358b83e23
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/details_page/details_row.vue
@@ -0,0 +1,26 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlIcon,
+ },
+ props: {
+ icon: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="gl-display-flex gl-align-items-center gl-font-monospace gl-font-sm gl-py-2 gl-word-break-all"
+ >
+ <gl-icon :name="icon" class="gl-mr-4" />
+ <span>
+ <slot></slot>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue
new file mode 100644
index 00000000000..8494967ab57
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue
@@ -0,0 +1,77 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import TagsListRow from './tags_list_row.vue';
+import { REMOVE_TAGS_BUTTON_TITLE, TAGS_LIST_TITLE } from '../../constants/index';
+
+export default {
+ components: {
+ GlButton,
+ TagsListRow,
+ },
+ props: {
+ tags: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ isDesktop: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ i18n: {
+ REMOVE_TAGS_BUTTON_TITLE,
+ TAGS_LIST_TITLE,
+ },
+ data() {
+ return {
+ selectedItems: {},
+ };
+ },
+ computed: {
+ hasSelectedItems() {
+ return this.tags.some(tag => this.selectedItems[tag.name]);
+ },
+ showMultiDeleteButton() {
+ return this.tags.some(tag => tag.destroy_path) && this.isDesktop;
+ },
+ },
+ methods: {
+ updateSelectedItems(name) {
+ this.$set(this.selectedItems, name, !this.selectedItems[name]);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-display-flex gl-justify-content-space-between gl-mb-3">
+ <h5 data-testid="list-title">
+ {{ $options.i18n.TAGS_LIST_TITLE }}
+ </h5>
+
+ <gl-button
+ v-if="showMultiDeleteButton"
+ :disabled="!hasSelectedItems"
+ category="secondary"
+ variant="danger"
+ @click="$emit('delete', selectedItems)"
+ >
+ {{ $options.i18n.REMOVE_TAGS_BUTTON_TITLE }}
+ </gl-button>
+ </div>
+ <tags-list-row
+ v-for="(tag, index) in tags"
+ :key="tag.path"
+ :tag="tag"
+ :first="index === 0"
+ :last="index === tags.length - 1"
+ :selected="selectedItems[tag.name]"
+ :is-desktop="isDesktop"
+ @select="updateSelectedItems(tag.name)"
+ @delete="$emit('delete', { [tag.name]: true })"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue
new file mode 100644
index 00000000000..51ba2337db6
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue
@@ -0,0 +1,220 @@
+<script>
+import { GlFormCheckbox, GlTooltipDirective, GlSprintf, GlIcon } from '@gitlab/ui';
+import { n__ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { formatDate } from '~/lib/utils/datetime_utility';
+import DeleteButton from '../delete_button.vue';
+import ListItem from '../list_item.vue';
+import DetailsRow from './details_row.vue';
+import {
+ REMOVE_TAG_BUTTON_TITLE,
+ DIGEST_LABEL,
+ CREATED_AT_LABEL,
+ REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ PUBLISHED_DETAILS_ROW_TEXT,
+ MANIFEST_DETAILS_ROW_TEST,
+ CONFIGURATION_DETAILS_ROW_TEST,
+ MISSING_MANIFEST_WARNING_TOOLTIP,
+ NOT_AVAILABLE_TEXT,
+ NOT_AVAILABLE_SIZE,
+} from '../../constants/index';
+
+export default {
+ components: {
+ GlSprintf,
+ GlFormCheckbox,
+ GlIcon,
+ DeleteButton,
+ ListItem,
+ ClipboardButton,
+ TimeAgoTooltip,
+ DetailsRow,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ tag: {
+ type: Object,
+ required: true,
+ },
+ isDesktop: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ selected: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ i18n: {
+ REMOVE_TAG_BUTTON_TITLE,
+ DIGEST_LABEL,
+ CREATED_AT_LABEL,
+ REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
+ PUBLISHED_DETAILS_ROW_TEXT,
+ MANIFEST_DETAILS_ROW_TEST,
+ CONFIGURATION_DETAILS_ROW_TEST,
+ MISSING_MANIFEST_WARNING_TOOLTIP,
+ },
+ computed: {
+ formattedSize() {
+ return this.tag.total_size ? numberToHumanSize(this.tag.total_size) : NOT_AVAILABLE_SIZE;
+ },
+ layers() {
+ return this.tag.layers ? n__('%d layer', '%d layers', this.tag.layers) : '';
+ },
+ mobileClasses() {
+ return this.isDesktop ? '' : 'mw-s';
+ },
+ shortDigest() {
+ // remove sha256: from the string, and show only the first 7 char
+ return this.tag.digest?.substring(7, 14) ?? NOT_AVAILABLE_TEXT;
+ },
+ publishedDate() {
+ return formatDate(this.tag.created_at, 'isoDate');
+ },
+ publishedTime() {
+ return formatDate(this.tag.created_at, 'hh:MM Z');
+ },
+ formattedRevision() {
+ // to be removed when API response is adjusted
+ // see https://gitlab.com/gitlab-org/gitlab/-/issues/225324
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `sha256:${this.tag.revision}`;
+ },
+ tagLocation() {
+ return this.tag.path?.replace(`:${this.tag.name}`, '');
+ },
+ invalidTag() {
+ return !this.tag.digest;
+ },
+ },
+};
+</script>
+
+<template>
+ <list-item v-bind="$attrs" :selected="selected">
+ <template #left-action>
+ <gl-form-checkbox
+ v-if="Boolean(tag.destroy_path)"
+ :disabled="invalidTag"
+ class="gl-m-0"
+ :checked="selected"
+ @change="$emit('select')"
+ />
+ </template>
+ <template #left-primary>
+ <div class="gl-display-flex gl-align-items-center">
+ <div
+ v-gl-tooltip="{ title: tag.name }"
+ data-testid="name"
+ class="gl-text-overflow-ellipsis gl-overflow-hidden gl-white-space-nowrap"
+ :class="mobileClasses"
+ >
+ {{ tag.name }}
+ </div>
+
+ <clipboard-button
+ v-if="tag.location"
+ :title="tag.location"
+ :text="tag.location"
+ css-class="btn-default btn-transparent btn-clipboard"
+ />
+
+ <gl-icon
+ v-if="invalidTag"
+ v-gl-tooltip="{ title: $options.i18n.MISSING_MANIFEST_WARNING_TOOLTIP }"
+ name="warning"
+ class="gl-text-orange-500 gl-mb-2 gl-ml-2"
+ />
+ </div>
+ </template>
+
+ <template #left-secondary>
+ <span data-testid="size">
+ {{ formattedSize }}
+ <template v-if="formattedSize && layers"
+ >&middot;</template
+ >
+ {{ layers }}
+ </span>
+ </template>
+ <template #right-primary>
+ <span data-testid="time">
+ <gl-sprintf :message="$options.i18n.CREATED_AT_LABEL">
+ <template #timeInfo>
+ <time-ago-tooltip :time="tag.created_at" />
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #right-secondary>
+ <span data-testid="digest">
+ <gl-sprintf :message="$options.i18n.DIGEST_LABEL">
+ <template #imageId>{{ shortDigest }}</template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #right-action>
+ <delete-button
+ :disabled="!tag.destroy_path || invalidTag"
+ :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
+ :tooltip-title="$options.i18n.REMOVE_TAG_BUTTON_DISABLE_TOOLTIP"
+ :tooltip-disabled="Boolean(tag.destroy_path)"
+ data-testid="single-delete-button"
+ @delete="$emit('delete')"
+ />
+ </template>
+
+ <template v-if="!invalidTag" #details_published>
+ <details-row icon="clock" data-testid="published-date-detail">
+ <gl-sprintf :message="$options.i18n.PUBLISHED_DETAILS_ROW_TEXT">
+ <template #repositoryPath>
+ <i>{{ tagLocation }}</i>
+ </template>
+ <template #time>
+ {{ publishedTime }}
+ </template>
+ <template #date>
+ {{ publishedDate }}
+ </template>
+ </gl-sprintf>
+ </details-row>
+ </template>
+ <template v-if="!invalidTag" #details_manifest_digest>
+ <details-row icon="log" data-testid="manifest-detail">
+ <gl-sprintf :message="$options.i18n.MANIFEST_DETAILS_ROW_TEST">
+ <template #digest>
+ {{ tag.digest }}
+ </template>
+ </gl-sprintf>
+ <clipboard-button
+ v-if="tag.digest"
+ :title="tag.digest"
+ :text="tag.digest"
+ css-class="btn-default btn-transparent btn-clipboard gl-p-0"
+ />
+ </details-row>
+ </template>
+ <template v-if="!invalidTag" #details_configuration_digest>
+ <details-row icon="cloud-gear" data-testid="configuration-detail">
+ <gl-sprintf :message="$options.i18n.CONFIGURATION_DETAILS_ROW_TEST">
+ <template #digest>
+ {{ formattedRevision }}
+ </template>
+ </gl-sprintf>
+ <clipboard-button
+ v-if="formattedRevision"
+ :title="formattedRevision"
+ :text="formattedRevision"
+ css-class="btn-default btn-transparent btn-clipboard gl-p-0"
+ />
+ </details-row>
+ </template>
+ </list-item>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue
deleted file mode 100644
index 81be778e1e5..00000000000
--- a/app/assets/javascripts/registry/explorer/components/details_page/tags_table.vue
+++ /dev/null
@@ -1,210 +0,0 @@
-<script>
-import { GlTable, GlFormCheckbox, GlButton, GlTooltipDirective } from '@gitlab/ui';
-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 {
- 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,
- REMOVE_TAGS_BUTTON_TITLE,
- REMOVE_TAG_BUTTON_TITLE,
-} from '../../constants/index';
-
-export default {
- components: {
- GlTable,
- GlFormCheckbox,
- GlButton,
- ClipboardButton,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- mixins: [timeagoMixin],
- props: {
- tags: {
- type: Array,
- required: false,
- default: () => [],
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
- isDesktop: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- i18n: {
- REMOVE_TAGS_BUTTON_TITLE,
- REMOVE_TAG_BUTTON_TITLE,
- },
- data() {
- return {
- selectedItems: [],
- };
- },
- computed: {
- 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);
- },
- tagsNames() {
- return this.tags.map(t => t.name);
- },
- selectAllChecked() {
- return this.selectedItems.length === this.tags.length && this.tags.length > 0;
- },
- },
- watch: {
- tagsNames: {
- immediate: false,
- handler(tagsNames) {
- this.selectedItems = this.selectedItems.filter(t => tagsNames.includes(t));
- },
- },
- },
- methods: {
- formatSize(size) {
- return numberToHumanSize(size);
- },
- layers(layers) {
- return layers ? n__('%d layer', '%d layers', layers) : '';
- },
- onSelectAllChange() {
- if (this.selectAllChecked) {
- this.selectedItems = [];
- } else {
- this.selectedItems = this.tags.map(x => x.name);
- }
- },
- updateSelectedItems(name) {
- const delIndex = this.selectedItems.findIndex(x => x === name);
-
- if (delIndex > -1) {
- this.selectedItems.splice(delIndex, 1);
- } else {
- this.selectedItems.push(name);
- }
- },
- },
-};
-</script>
-
-<template>
- <gl-table :items="tags" :fields="fields" :stacked="!isDesktop" show-empty :busy="isLoading">
- <template v-if="isDesktop" #head(checkbox)>
- <gl-form-checkbox
- data-testid="mainCheckbox"
- :checked="selectAllChecked"
- @change="onSelectAllChange"
- />
- </template>
- <template #head(actions)>
- <span class="gl-display-flex gl-justify-content-end">
- <gl-button
- v-gl-tooltip
- data-testid="bulkDeleteButton"
- :disabled="!selectedItems || selectedItems.length === 0"
- icon="remove"
- variant="danger"
- :title="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
- :aria-label="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
- @click="$emit('delete', selectedItems)"
- />
- </span>
- </template>
-
- <template #cell(checkbox)="{item}">
- <gl-form-checkbox
- data-testid="rowCheckbox"
- :checked="selectedItems.includes(item.name)"
- @change="updateSelectedItems(item.name)"
- />
- </template>
- <template #cell(name)="{item, field}">
- <div data-testid="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"
- data-testid="rowClipboardButton"
- :title="item.location"
- :text="item.location"
- css-class="btn-default btn-transparent btn-clipboard"
- />
- </div>
- </template>
- <template #cell(short_revision)="{value}">
- <span data-testid="rowShortRevision">
- {{ value }}
- </span>
- </template>
- <template #cell(total_size)="{item}">
- <span data-testid="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 v-gl-tooltip data-testid="rowTime" :title="tooltipTitle(value)">
- {{ timeFormatted(value) }}
- </span>
- </template>
- <template #cell(actions)="{item}">
- <span class="gl-display-flex gl-justify-content-end">
- <gl-button
- data-testid="singleDeleteButton"
- :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
- :aria-label="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
- :disabled="!item.destroy_path"
- variant="danger"
- icon="remove"
- category="secondary"
- @click="$emit('delete', [item.name])"
- />
- </span>
- </template>
-
- <template #empty>
- <slot name="empty"></slot>
- </template>
- <template #table-busy>
- <slot name="loader"></slot>
- </template>
- </gl-table>
-</template>