summaryrefslogtreecommitdiff
path: root/app/assets
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-28 18:09:07 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-28 18:09:07 +0000
commit1c8fa70f9d0818e2a82089c8643a6e455bca47fd (patch)
treef339f97de0425270bdd909e2f4d378927b6e0a18 /app/assets
parent736d36d8597d0d1ec1b47644e6d091c3f4a78f45 (diff)
downloadgitlab-ce-1c8fa70f9d0818e2a82089c8643a6e455bca47fd.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js4
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue260
-rw-r--r--app/assets/javascripts/registry/explorer/pages/list.vue141
-rw-r--r--app/assets/javascripts/registry/explorer/stores/actions.js13
-rw-r--r--app/assets/javascripts/registry/explorer/stores/getters.js6
-rw-r--r--app/assets/javascripts/registry/explorer/stores/index.js2
-rw-r--r--app/assets/stylesheets/utilities.scss2
8 files changed, 241 insertions, 190 deletions
diff --git a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
index 479c82265f2..1ef18b356f2 100644
--- a/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/groups/settings/ci_cd/show/index.js
@@ -1,10 +1,13 @@
import initSettingsPanels from '~/settings_panels';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import initVariableList from '~/ci_variable_list';
+import DueDateSelectors from '~/due_date_select';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
+ // eslint-disable-next-line no-new
+ new DueDateSelectors();
if (gon.features.newVariablesUi) {
initVariableList();
diff --git a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
index e08d0407245..c83e2bdbf38 100644
--- a/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
+++ b/app/assets/javascripts/pages/projects/settings/ci_cd/show/index.js
@@ -3,6 +3,7 @@ import SecretValues from '~/behaviors/secret_values';
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initVariableList from '~/ci_variable_list';
+import DueDateSelectors from '~/due_date_select';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
@@ -39,5 +40,8 @@ document.addEventListener('DOMContentLoaded', () => {
autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
});
+ // eslint-disable-next-line no-new
+ new DueDateSelectors();
+
registrySettingsApp();
});
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index bfb9b0f4688..0f4ed1550ce 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -1,5 +1,5 @@
<script>
-import { mapState, mapActions } from 'vuex';
+import { mapState, mapActions, mapGetters } from 'vuex';
import {
GlTable,
GlFormCheckbox,
@@ -8,10 +8,10 @@ import {
GlTooltipDirective,
GlPagination,
GlModal,
- GlLoadingIcon,
GlSprintf,
GlEmptyState,
GlResizeObserverDirective,
+ GlSkeletonLoader,
} from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { n__, s__ } from '~/locale';
@@ -42,7 +42,7 @@ export default {
ClipboardButton,
GlPagination,
GlModal,
- GlLoadingIcon,
+ GlSkeletonLoader,
GlSprintf,
GlEmptyState,
},
@@ -51,6 +51,11 @@ export default {
GlResizeObserver: GlResizeObserverDirective,
},
mixins: [timeagoMixin, Tracking.mixin()],
+ loader: {
+ repeat: 10,
+ width: 1000,
+ height: 40,
+ },
data() {
return {
selectedItems: [],
@@ -61,15 +66,16 @@ export default {
};
},
computed: {
- ...mapState(['tags', 'tagsPagination', 'isLoading', 'config']),
+ ...mapGetters(['tags']),
+ ...mapState(['tagsPagination', 'isLoading', 'config']),
imageName() {
const { name } = decodeAndParse(this.$route.params.id);
return name;
},
fields() {
return [
- { key: LIST_KEY_CHECKBOX, label: '' },
- { key: LIST_KEY_TAG, label: LIST_LABEL_TAG },
+ { key: LIST_KEY_CHECKBOX, label: '', class: 'gl-w-16' },
+ { key: LIST_KEY_TAG, label: LIST_LABEL_TAG, class: 'w-25' },
{ 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 },
@@ -209,122 +215,142 @@ export default {
</gl-sprintf>
</h4>
</div>
- <gl-loading-icon v-if="isLoading" />
- <template v-else-if="tags.length > 0">
- <gl-table :items="tags" :fields="fields" :stacked="!isDesktop">
- <template v-if="isDesktop" #head(checkbox)>
- <gl-form-checkbox
- ref="mainCheckbox"
- :checked="selectAllChecked"
- @change="onSelectAllChange"
- />
- </template>
- <template #head(actions)>
- <gl-button
- ref="bulkDeleteButton"
- v-gl-tooltip
- :disabled="!selectedItems || selectedItems.length === 0"
- class="float-right"
- variant="danger"
- :title="s__('ContainerRegistry|Remove selected tags')"
- :aria-label="s__('ContainerRegistry|Remove selected tags')"
- @click="deleteMultipleItems()"
- >
- <gl-icon name="remove" />
- </gl-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}">
- <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>
- <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">
- {{ timeFormatted(value) }}
- </span>
- </template>
- <template #cell(actions)="{index, item}">
- <gl-button
- ref="singleDeleteButton"
- :title="s__('ContainerRegistry|Remove tag')"
- :aria-label="s__('ContainerRegistry|Remove tag')"
- :disabled="!item.destroy_path"
- variant="danger"
- :class="['js-delete-registry float-right btn-inverted btn-border-color btn-icon']"
- @click="deleteSingleItem(index)"
+ <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-button
+ ref="bulkDeleteButton"
+ v-gl-tooltip
+ :disabled="!selectedItems || selectedItems.length === 0"
+ class="float-right"
+ variant="danger"
+ :title="s__('ContainerRegistry|Remove selected tags')"
+ :aria-label="s__('ContainerRegistry|Remove selected tags')"
+ @click="deleteMultipleItems()"
+ >
+ <gl-icon name="remove" />
+ </gl-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}">
+ <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>
+ <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">
+ {{ timeFormatted(value) }}
+ </span>
+ </template>
+ <template #cell(actions)="{index, item}">
+ <gl-button
+ ref="singleDeleteButton"
+ :title="s__('ContainerRegistry|Remove tag')"
+ :aria-label="s__('ContainerRegistry|Remove tag')"
+ :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-button>
+ </template>
+
+ <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"
>
- <gl-icon name="remove" />
- </gl-button>
+ <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-table>
- <gl-pagination
- ref="pagination"
- v-model="currentPage"
- :per-page="tagsPagination.perPage"
- :total-items="tagsPagination.total"
- align="center"
- class="w-100"
- />
- <gl-modal
- ref="deleteModal"
- modal-id="delete-tag-modal"
- ok-variant="danger"
- @ok="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>
- </template>
- <gl-empty-state
- v-else
- :title="s__('ContainerRegistry|This image has no active tags')"
- :svg-path="config.noContainersImage"
- :description="
- s__(
- `ContainerRegistry|The last tag related to this image was recently removed.
+ <gl-empty-state
+ v-else
+ :title="s__('ContainerRegistry|This image has no active tags')"
+ :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.`,
- )
- "
- class="mx-auto my-0"
+ )
+ "
+ class="mx-auto my-0"
+ />
+ </template>
+ </gl-table>
+
+ <gl-pagination
+ ref="pagination"
+ v-model="currentPage"
+ :per-page="tagsPagination.perPage"
+ :total-items="tagsPagination.total"
+ align="center"
+ class="w-100"
/>
+
+ <gl-modal
+ ref="deleteModal"
+ modal-id="delete-tag-modal"
+ ok-variant="danger"
+ @ok="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>
diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue
index 5f8f4d8df1e..4e9f0a83501 100644
--- a/app/assets/javascripts/registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/registry/explorer/pages/list.vue
@@ -1,7 +1,6 @@
<script>
import { mapState, mapActions } from 'vuex';
import {
- GlLoadingIcon,
GlEmptyState,
GlPagination,
GlTooltipDirective,
@@ -10,6 +9,7 @@ import {
GlModal,
GlSprintf,
GlLink,
+ GlSkeletonLoader,
} from '@gitlab/ui';
import Tracking from '~/tracking';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
@@ -20,7 +20,6 @@ export default {
name: 'RegistryListApp',
components: {
GlEmptyState,
- GlLoadingIcon,
GlPagination,
ProjectEmptyState,
GroupEmptyState,
@@ -30,11 +29,17 @@ export default {
GlModal,
GlSprintf,
GlLink,
+ GlSkeletonLoader,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin()],
+ loader: {
+ repeat: 10,
+ width: 1000,
+ height: 40,
+ },
data() {
return {
itemToDelete: {},
@@ -104,74 +109,81 @@ export default {
</gl-empty-state>
<template v-else>
- <gl-loading-icon v-if="isLoading" size="md" class="prepend-top-16" />
-
- <template v-else>
- <div v-if="images.length" ref="imagesList">
- <h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
- <p>
- <gl-sprintf
- :message="
- s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
+ <div>
+ <h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
+ <p>
+ <gl-sprintf
+ :message="
+ s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
project can have its own space to store its Docker images.
%{docLinkStart}More Information%{docLinkEnd}`)
- "
- >
- <template #docLink="{content}">
- <gl-link :href="config.helpPagePath" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </p>
+ "
+ >
+ <template #docLink="{content}">
+ <gl-link :href="config.helpPagePath" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ </div>
- <div class="d-flex flex-column">
+ <div v-if="isLoading" class="mt-2">
+ <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>
+ <div v-if="images.length" ref="imagesList" class="d-flex flex-column">
+ <div
+ v-for="(listItem, index) in images"
+ :key="index"
+ ref="rowItem"
+ :class="{ 'border-top': index === 0 }"
+ class="d-flex justify-content-between align-items-center py-2 border-bottom"
+ >
+ <div>
+ <router-link
+ ref="detailsLink"
+ :to="{ name: 'details', params: { id: encodeListItem(listItem) } }"
+ >
+ {{ listItem.path }}
+ </router-link>
+ <clipboard-button
+ v-if="listItem.location"
+ ref="clipboardButton"
+ :text="listItem.location"
+ :title="listItem.location"
+ css-class="btn-default btn-transparent btn-clipboard"
+ />
+ </div>
<div
- v-for="(listItem, index) in images"
- :key="index"
- ref="rowItem"
- :class="[
- 'd-flex justify-content-between align-items-center py-2 border-bottom',
- { 'border-top': index === 0 },
- ]"
+ v-gl-tooltip="{ disabled: listItem.destroy_path }"
+ class="d-none d-sm-block"
+ :title="
+ s__('ContainerRegistry|Missing or insufficient permission, delete button disabled')
+ "
>
- <div>
- <router-link
- ref="detailsLink"
- :to="{ name: 'details', params: { id: encodeListItem(listItem) } }"
- >
- {{ listItem.path }}
- </router-link>
- <clipboard-button
- v-if="listItem.location"
- ref="clipboardButton"
- :text="listItem.location"
- :title="listItem.location"
- css-class="btn-default btn-transparent btn-clipboard"
- />
- </div>
- <div
- v-gl-tooltip="{ disabled: listItem.destroy_path }"
- class="d-none d-sm-block"
- :title="
- s__(
- 'ContainerRegistry|Missing or insufficient permission, delete button disabled',
- )
- "
+ <gl-button
+ ref="deleteImageButton"
+ v-gl-tooltip
+ :disabled="!listItem.destroy_path"
+ :title="s__('ContainerRegistry|Remove repository')"
+ :aria-label="s__('ContainerRegistry|Remove repository')"
+ class="btn-inverted"
+ variant="danger"
+ @click="deleteImage(listItem)"
>
- <gl-button
- ref="deleteImageButton"
- v-gl-tooltip
- :disabled="!listItem.destroy_path"
- :title="s__('ContainerRegistry|Remove repository')"
- :aria-label="s__('ContainerRegistry|Remove repository')"
- class="btn-inverted"
- variant="danger"
- @click="deleteImage(listItem)"
- >
- <gl-icon name="remove" />
- </gl-button>
- </div>
+ <gl-icon name="remove" />
+ </gl-button>
</div>
</div>
<gl-pagination
@@ -182,6 +194,7 @@ export default {
class="w-100 mt-2"
/>
</div>
+
<template v-else>
<project-empty-state v-if="!config.isGroupPage" />
<group-empty-state v-else />
diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js
index 86d00d4fca9..7d8201949f4 100644
--- a/app/assets/javascripts/registry/explorer/stores/actions.js
+++ b/app/assets/javascripts/registry/explorer/stores/actions.js
@@ -68,31 +68,28 @@ export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) =
.delete(tag.destroy_path)
.then(() => {
createFlash(DELETE_TAG_SUCCESS_MESSAGE, 'success');
- dispatch('requestTagsList', { pagination: state.tagsPagination, params });
+ return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
createFlash(DELETE_TAG_ERROR_MESSAGE);
- })
- .finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params }) => {
commit(types.SET_MAIN_LOADING, true);
- const { id } = decodeAndParse(params);
- const url = `/${state.config.projectPath}/registry/repository/${id}/tags/bulk_destroy`;
+ const { tags_path } = decodeAndParse(params);
+
+ const url = tags_path.replace('?format=json', '/bulk_destroy');
return axios
.delete(url, { params: { ids } })
.then(() => {
createFlash(DELETE_TAGS_SUCCESS_MESSAGE, 'success');
- dispatch('requestTagsList', { pagination: state.tagsPagination, params });
+ return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
createFlash(DELETE_TAGS_ERROR_MESSAGE);
- })
- .finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
diff --git a/app/assets/javascripts/registry/explorer/stores/getters.js b/app/assets/javascripts/registry/explorer/stores/getters.js
new file mode 100644
index 00000000000..5619b73d495
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/stores/getters.js
@@ -0,0 +1,6 @@
+// eslint-disable-next-line import/prefer-default-export
+export const tags = state => {
+ // to show the loader inside the table we need to pass an empty array to gl-table whenever the table is loading
+ // this is to take in account isLoading = true and state.tags =[1,2,3] during pagination and delete
+ return state.isLoading ? [] : state.tags;
+};
diff --git a/app/assets/javascripts/registry/explorer/stores/index.js b/app/assets/javascripts/registry/explorer/stores/index.js
index 91a35aac149..b3ff2e6e002 100644
--- a/app/assets/javascripts/registry/explorer/stores/index.js
+++ b/app/assets/javascripts/registry/explorer/stores/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
+import * as getters from './getters';
import mutations from './mutations';
import state from './state';
@@ -9,6 +10,7 @@ Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
state,
+ getters,
actions,
mutations,
});
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index dabbcf0eac1..2a92b271ed0 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -54,7 +54,7 @@
.mh-50vh { max-height: 50vh; }
.font-size-inherit { font-size: inherit; }
-
+.gl-w-16 { width: px-to-rem($grid-size * 2); }
.gl-w-64 { width: px-to-rem($grid-size * 8); }
.gl-h-32 { height: px-to-rem($grid-size * 4); }
.gl-h-64 { height: px-to-rem($grid-size * 8); }