diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-17 12:07:33 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-17 12:07:33 +0000 |
commit | 6b75320f525f841454f1ab162d141d3610f2e77b (patch) | |
tree | 4971c27759e4fbc18b85e71800c3b9c12346317e /app/assets/javascripts/registry | |
parent | 4226aca420920c1844e8eade4798a2dff188a6fc (diff) | |
download | gitlab-ce-6b75320f525f841454f1ab162d141d3610f2e77b.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/registry')
11 files changed, 253 insertions, 113 deletions
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue index a20bae9e37e..11b2c3b7016 100644 --- a/app/assets/javascripts/registry/components/app.vue +++ b/app/assets/javascripts/registry/components/app.vue @@ -2,17 +2,19 @@ import { mapGetters, mapActions } from 'vuex'; import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui'; import store from '../stores'; -import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; import CollapsibleContainer from './collapsible_container.vue'; +import ProjectEmptyState from './project_empty_state.vue'; +import GroupEmptyState from './group_empty_state.vue'; import { s__, sprintf } from '../../locale'; export default { name: 'RegistryListApp', components: { - clipboardButton, CollapsibleContainer, GlEmptyState, GlLoadingIcon, + ProjectEmptyState, + GroupEmptyState, }, props: { characterError: { @@ -38,19 +40,27 @@ export default { }, personalAccessTokensHelpLink: { type: String, - required: true, + required: false, + default: null, }, registryHostUrlWithPort: { type: String, - required: true, + required: false, + default: null, }, repositoryUrl: { type: String, required: true, }, + isGroupPage: { + type: Boolean, + default: false, + required: false, + }, twoFactorAuthHelpLink: { type: String, - required: true, + required: false, + default: null, }, }, store, @@ -91,37 +101,10 @@ export default { false, ); }, - notLoggedInToRegistryText() { - return sprintf( - s__(`ContainerRegistry|If you are not already logged in, you need to authenticate to - the Container Registry by using your GitLab username and password. If you have - %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a - %{personalAccessTokensDocLinkStart}Personal Access Token - %{personalAccessTokensDocLinkEnd}instead of a password.`), - { - twofaDocLinkStart: `<a href="${this.twoFactorAuthHelpLink}" target="_blank">`, - twofaDocLinkEnd: '</a>', - personalAccessTokensDocLinkStart: `<a href="${this.personalAccessTokensHelpLink}" target="_blank">`, - personalAccessTokensDocLinkEnd: '</a>', - }, - false, - ); - }, - dockerLoginCommand() { - // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings - return `docker login ${this.registryHostUrlWithPort}`; - }, - dockerBuildCommand() { - // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings - return `docker build -t ${this.repositoryUrl} .`; - }, - dockerPushCommand() { - // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings - return `docker push ${this.repositoryUrl}`; - }, }, created() { this.setMainEndpoint(this.endpoint); + this.setIsDeleteDisabled(this.isGroupPage); }, mounted() { if (!this.characterError) { @@ -129,7 +112,7 @@ export default { } }, methods: { - ...mapActions(['setMainEndpoint', 'fetchRepos']), + ...mapActions(['setMainEndpoint', 'fetchRepos', 'setIsDeleteDisabled']), }, }; </script> @@ -152,57 +135,19 @@ export default { <p v-html="introText"></p> <collapsible-container v-for="item in repos" :key="item.id" :repo="item" /> </div> - - <gl-empty-state - v-else - :title="s__('ContainerRegistry|There are no container images stored for this project')" - :svg-path="noContainersImage" - class="container-message" - > - <template #description> - <p class="js-no-container-images-text" v-html="noContainerImagesText"></p> - <h5>{{ s__('ContainerRegistry|Quick Start') }}</h5> - <p class="js-not-logged-in-to-registry-text" v-html="notLoggedInToRegistryText"></p> - <div class="input-group append-bottom-10"> - <input :value="dockerLoginCommand" type="text" class="form-control monospace" readonly /> - <span class="input-group-append"> - <clipboard-button - :text="dockerLoginCommand" - :title="s__('ContainerRegistry|Copy login command')" - class="input-group-text" - /> - </span> - </div> - <p> - {{ - s__( - 'ContainerRegistry|You can add an image to this registry with the following commands:', - ) - }} - </p> - - <div class="input-group append-bottom-10"> - <input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly /> - <span class="input-group-append"> - <clipboard-button - :text="dockerBuildCommand" - :title="s__('ContainerRegistry|Copy build command')" - class="input-group-text" - /> - </span> - </div> - - <div class="input-group"> - <input :value="dockerPushCommand" type="text" class="form-control monospace" readonly /> - <span class="input-group-append"> - <clipboard-button - :text="dockerPushCommand" - :title="s__('ContainerRegistry|Copy push command')" - class="input-group-text" - /> - </span> - </div> - </template> - </gl-empty-state> + <project-empty-state + v-else-if="!isGroupPage" + :no-containers-image="noContainersImage" + :help-page-path="helpPagePath" + :repository-url="repositoryUrl" + :two-factor-auth-help-link="twoFactorAuthHelpLink" + :personal-access-tokens-help-link="personalAccessTokensHelpLink" + :registry-host-url-with-port="registryHostUrlWithPort" + /> + <group-empty-state + v-else-if="isGroupPage" + :no-containers-image="noContainersImage" + :help-page-path="helpPagePath" + /> </div> </template> diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index 3e31d24088e..ed48331f459 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -1,5 +1,5 @@ <script> -import { mapActions } from 'vuex'; +import { mapActions, mapGetters } from 'vuex'; import { GlLoadingIcon, GlButton, GlTooltipDirective, GlModal, GlModalDirective } from '@gitlab/ui'; import createFlash from '../../flash'; import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; @@ -35,9 +35,13 @@ export default { }; }, computed: { + ...mapGetters(['isDeleteDisabled']), iconName() { return this.isOpen ? 'angle-up' : 'angle-right'; }, + canDeleteRepo() { + return this.repo.canDelete && !this.isDeleteDisabled; + }, }, methods: { ...mapActions(['fetchRepos', 'fetchList', 'deleteItem']), @@ -80,7 +84,7 @@ export default { <div class="controls d-none d-sm-block float-right"> <gl-button - v-if="repo.canDelete" + v-if="canDeleteRepo" v-gl-tooltip v-gl-modal="modalId" :title="s__('ContainerRegistry|Remove repository')" @@ -98,7 +102,7 @@ export default { <gl-loading-icon v-if="repo.isLoading" size="md" class="append-bottom-20" /> <div v-else-if="!repo.isLoading && isOpen" class="container-image-tags"> - <table-registry v-if="repo.list.length" :repo="repo" /> + <table-registry v-if="repo.list.length" :repo="repo" :can-delete-repo="canDeleteRepo" /> <div v-else class="nothing-here-block"> {{ s__('ContainerRegistry|No tags in Container Registry for this container image.') }} diff --git a/app/assets/javascripts/registry/components/group_empty_state.vue b/app/assets/javascripts/registry/components/group_empty_state.vue new file mode 100644 index 00000000000..7885fd2146d --- /dev/null +++ b/app/assets/javascripts/registry/components/group_empty_state.vue @@ -0,0 +1,46 @@ +<script> +import { GlEmptyState } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; + +export default { + name: 'GroupEmptyState', + components: { + GlEmptyState, + }, + props: { + noContainersImage: { + type: String, + required: true, + }, + helpPagePath: { + type: String, + required: true, + }, + }, + computed: { + noContainerImagesText() { + return sprintf( + s__( + `ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}`, + ), + { + docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`, + docLinkEnd: '</a>', + }, + false, + ); + }, + }, +}; +</script> +<template> + <gl-empty-state + :title="s__('ContainerRegistry|There are no container images available in this group')" + :svg-path="noContainersImage" + class="container-message" + > + <template #description> + <p class="js-no-container-images-text" v-html="noContainerImagesText"></p> + </template> + </gl-empty-state> +</template> diff --git a/app/assets/javascripts/registry/components/project_empty_state.vue b/app/assets/javascripts/registry/components/project_empty_state.vue new file mode 100644 index 00000000000..80ef31004c8 --- /dev/null +++ b/app/assets/javascripts/registry/components/project_empty_state.vue @@ -0,0 +1,133 @@ +<script> +import { GlEmptyState } from '@gitlab/ui'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { s__, sprintf } from '~/locale'; + +export default { + name: 'ProjectEmptyState', + components: { + ClipboardButton, + GlEmptyState, + }, + props: { + noContainersImage: { + type: String, + required: true, + }, + repositoryUrl: { + type: String, + required: true, + }, + helpPagePath: { + type: String, + required: true, + }, + twoFactorAuthHelpLink: { + type: String, + required: true, + }, + personalAccessTokensHelpLink: { + type: String, + required: true, + }, + registryHostUrlWithPort: { + type: String, + required: true, + }, + }, + computed: { + dockerBuildCommand() { + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings + return `docker build -t ${this.repositoryUrl} .`; + }, + dockerPushCommand() { + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings + return `docker push ${this.repositoryUrl}`; + }, + dockerLoginCommand() { + // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings + return `docker login ${this.registryHostUrlWithPort}`; + }, + noContainerImagesText() { + return sprintf( + s__(`ContainerRegistry|With the Container Registry, every project can have its own space to + store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`), + { + docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`, + docLinkEnd: '</a>', + }, + false, + ); + }, + notLoggedInToRegistryText() { + return sprintf( + s__(`ContainerRegistry|If you are not already logged in, you need to authenticate to + the Container Registry by using your GitLab username and password. If you have + %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a + %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} + instead of a password.`), + { + twofaDocLinkStart: `<a href="${this.twoFactorAuthHelpLink}" target="_blank">`, + twofaDocLinkEnd: '</a>', + personalAccessTokensDocLinkStart: `<a href="${this.personalAccessTokensHelpLink}" target="_blank">`, + personalAccessTokensDocLinkEnd: '</a>', + }, + false, + ); + }, + }, +}; +</script> +<template> + <gl-empty-state + :title="s__('ContainerRegistry|There are no container images stored for this project')" + :svg-path="noContainersImage" + class="container-message" + > + <template #description> + <p class="js-no-container-images-text" v-html="noContainerImagesText"></p> + <h5>{{ s__('ContainerRegistry|Quick Start') }}</h5> + <p class="js-not-logged-in-to-registry-text" v-html="notLoggedInToRegistryText"></p> + <div class="input-group append-bottom-10"> + <input :value="dockerLoginCommand" type="text" class="form-control monospace" readonly /> + <span class="input-group-append"> + <clipboard-button + :text="dockerLoginCommand" + :title="s__('ContainerRegistry|Copy login command')" + class="input-group-text" + /> + </span> + </div> + <p></p> + <p> + {{ + s__( + 'ContainerRegistry|You can add an image to this registry with the following commands:', + ) + }} + </p> + + <div class="input-group append-bottom-10"> + <input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly /> + <span class="input-group-append"> + <clipboard-button + :text="dockerBuildCommand" + :title="s__('ContainerRegistry|Copy build command')" + class="input-group-text" + /> + </span> + </div> + + <div class="input-group"> + <input :value="dockerPushCommand" type="text" class="form-control monospace" readonly /> + <span class="input-group-append"> + <clipboard-button + :text="dockerPushCommand" + :title="s__('ContainerRegistry|Copy push command')" + class="input-group-text" + /> + </span> + </div> + </template> + </gl-empty-state> +</template> diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index 00acc0eb04a..ac7272c4d29 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -1,5 +1,5 @@ <script> -import { mapActions } from 'vuex'; +import { mapActions, mapGetters } from 'vuex'; import { GlButton, GlFormCheckbox, @@ -35,6 +35,11 @@ export default { type: Object, required: true, }, + canDeleteRepo: { + type: Boolean, + default: false, + required: false, + }, }, data() { return { @@ -45,6 +50,7 @@ export default { }; }, computed: { + ...mapGetters(['isDeleteDisabled']), bulkDeletePath() { return this.repo.tagsPath ? this.repo.tagsPath.replace('?format=json', '/bulk_destroy') : ''; }, @@ -165,6 +171,9 @@ export default { } } }, + canDeleteRow(item) { + return item && item.canDelete && !this.isDeleteDisabled; + }, }, }; </script> @@ -175,7 +184,7 @@ export default { <tr> <th> <gl-form-checkbox - v-if="repo.canDelete" + v-if="canDeleteRepo" class="js-select-all-checkbox" :checked="selectAllChecked" @change="onSelectAllChange" @@ -187,7 +196,7 @@ export default { <th>{{ s__('ContainerRegistry|Last Updated') }}</th> <th> <gl-button - v-if="repo.canDelete" + v-if="canDeleteRepo" v-gl-tooltip v-gl-modal="modalId" :disabled="!itemsToBeDeleted || itemsToBeDeleted.length === 0" @@ -208,7 +217,7 @@ export default { <tr v-for="(item, index) in repo.list" :key="item.tag" class="registry-image-row"> <td class="check"> <gl-form-checkbox - v-if="item.canDelete" + v-if="canDeleteRow(item)" class="js-select-checkbox" :checked="itemsToBeDeleted && itemsToBeDeleted.includes(index)" @change="updateItemsToBeDeleted(index)" @@ -244,7 +253,7 @@ export default { <td class="content action-buttons"> <gl-button - v-if="item.canDelete" + v-if="canDeleteRow(item)" v-gl-modal="modalId" :title="s__('ContainerRegistry|Remove tag')" :aria-label="s__('ContainerRegistry|Remove tag')" diff --git a/app/assets/javascripts/registry/index.js b/app/assets/javascripts/registry/index.js index 38c3d67042c..18fd360f586 100644 --- a/app/assets/javascripts/registry/index.js +++ b/app/assets/javascripts/registry/index.js @@ -13,29 +13,24 @@ export default () => data() { const { dataset } = document.querySelector(this.$options.el); return { - characterError: Boolean(dataset.characterError), - containersErrorImage: dataset.containersErrorImage, - endpoint: dataset.endpoint, - helpPagePath: dataset.helpPagePath, - noContainersImage: dataset.noContainersImage, - personalAccessTokensHelpLink: dataset.personalAccessTokensHelpLink, - registryHostUrlWithPort: dataset.registryHostUrlWithPort, - repositoryUrl: dataset.repositoryUrl, - twoFactorAuthHelpLink: dataset.twoFactorAuthHelpLink, + registryData: { + endpoint: dataset.endpoint, + characterError: Boolean(dataset.characterError), + helpPagePath: dataset.helpPagePath, + noContainersImage: dataset.noContainersImage, + containersErrorImage: dataset.containersErrorImage, + repositoryUrl: dataset.repositoryUrl, + isGroupPage: dataset.isGroupPage, + personalAccessTokensHelpLink: dataset.personalAccessTokensHelpLink, + registryHostUrlWithPort: dataset.registryHostUrlWithPort, + twoFactorAuthHelpLink: dataset.twoFactorAuthHelpLink, + }, }; }, render(createElement) { return createElement('registry-app', { props: { - characterError: this.characterError, - containersErrorImage: this.containersErrorImage, - endpoint: this.endpoint, - helpPagePath: this.helpPagePath, - noContainersImage: this.noContainersImage, - personalAccessTokensHelpLink: this.personalAccessTokensHelpLink, - registryHostUrlWithPort: this.registryHostUrlWithPort, - repositoryUrl: this.repositoryUrl, - twoFactorAuthHelpLink: this.twoFactorAuthHelpLink, + ...this.registryData, }, }); }, diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js index a2e0130e79e..2121f518a7a 100644 --- a/app/assets/javascripts/registry/stores/actions.js +++ b/app/assets/javascripts/registry/stores/actions.js @@ -20,7 +20,6 @@ export const fetchRepos = ({ commit, state }) => { export const fetchList = ({ commit }, { repo, page }) => { commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo); - return axios .get(repo.tagsPath, { params: { page } }) .then(response => { @@ -40,6 +39,7 @@ export const multiDeleteItems = (_, { path, items }) => axios.delete(path, { params: { ids: items } }); export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data); +export const setIsDeleteDisabled = ({ commit }, data) => commit(types.SET_IS_DELETE_DISABLED, data); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING); // prevent babel-plugin-rewire from generating an invalid default during karma tests diff --git a/app/assets/javascripts/registry/stores/getters.js b/app/assets/javascripts/registry/stores/getters.js index f4923512578..ac90bde1b2a 100644 --- a/app/assets/javascripts/registry/stores/getters.js +++ b/app/assets/javascripts/registry/stores/getters.js @@ -1,5 +1,6 @@ export const isLoading = state => state.isLoading; export const repos = state => state.repos; +export const isDeleteDisabled = state => state.isDeleteDisabled; // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/registry/stores/mutation_types.js b/app/assets/javascripts/registry/stores/mutation_types.js index 2c69bf11807..6740bfede1a 100644 --- a/app/assets/javascripts/registry/stores/mutation_types.js +++ b/app/assets/javascripts/registry/stores/mutation_types.js @@ -1,4 +1,5 @@ export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT'; +export const SET_IS_DELETE_DISABLED = 'SET_IS_DELETE_DISABLED'; export const SET_REPOS_LIST = 'SET_REPOS_LIST'; export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING'; diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js index 8ace6657ad1..ea5925247d1 100644 --- a/app/assets/javascripts/registry/stores/mutations.js +++ b/app/assets/javascripts/registry/stores/mutations.js @@ -6,6 +6,10 @@ export default { Object.assign(state, { endpoint }); }, + [types.SET_IS_DELETE_DISABLED](state, isDeleteDisabled) { + Object.assign(state, { isDeleteDisabled }); + }, + [types.SET_REPOS_LIST](state, list) { Object.assign(state, { repos: list.map(el => ({ @@ -17,6 +21,7 @@ export default { location: el.location, name: el.path, tagsPath: el.tags_path, + projectId: el.project_id, })), }); }, diff --git a/app/assets/javascripts/registry/stores/state.js b/app/assets/javascripts/registry/stores/state.js index feeac10cbe1..724c64b4994 100644 --- a/app/assets/javascripts/registry/stores/state.js +++ b/app/assets/javascripts/registry/stores/state.js @@ -1,6 +1,7 @@ export default () => ({ isLoading: false, endpoint: '', // initial endpoint to fetch the repos list + isDeleteDisabled: false, // controls the delete buttons in the registry /** * Each object in `repos` has the following strucure: * { |