Skip to content
Snippets Groups Projects
Commit 6b75320f authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 4226aca4
No related branches found
No related tags found
No related merge requests found
Showing
with 309 additions and 130 deletions
10.1.0
10.2.0
Loading
Loading
@@ -26,7 +26,7 @@ export default {
 
if (log.append) {
if (isNewJobLogActive()) {
state.trace = updateIncrementalTrace(log.lines, state.trace);
state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
} else {
state.trace += log.html;
}
Loading
Loading
@@ -35,9 +35,9 @@ export default {
// When the job still does not have a trace
// the trace response will not have a defined
// html or size. We keep the old value otherwise these
// will be set to `undefined`
// will be set to `null`
if (isNewJobLogActive()) {
state.trace = logLinesParser(log.lines) || state.trace;
state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
} else {
state.trace = log.html || state.trace;
}
Loading
Loading
Loading
Loading
@@ -147,13 +147,15 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => {
 
const firstNew = newLog[0];
 
if (last.offset === firstNew.offset || (last.line && last.line.offset === firstNew.offset)) {
cloneOldLog.splice(lastIndex);
} else if (last.lines && last.lines.length) {
const lastNestedIndex = last.lines.length - 1;
const lastNested = last.lines[lastNestedIndex];
if (lastNested.offset === firstNew.offset) {
last.lines.splice(lastNestedIndex);
if (last && firstNew) {
if (last.offset === firstNew.offset || (last.line && last.line.offset === firstNew.offset)) {
cloneOldLog.splice(lastIndex);
} else if (last.lines && last.lines.length) {
const lastNestedIndex = last.lines.length - 1;
const lastNested = last.lines[lastNestedIndex];
if (lastNested.offset === firstNew.offset) {
last.lines.splice(lastNestedIndex);
}
}
}
 
Loading
Loading
@@ -170,7 +172,7 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => {
* @param array oldLog
* @param array newLog
*/
export const updateIncrementalTrace = (newLog, oldParsed = []) => {
export const updateIncrementalTrace = (newLog = [], oldParsed = []) => {
const parsedLog = findOffsetAndRemove(newLog, oldParsed);
 
return logLinesParser(newLog, parsedLog);
Loading
Loading
import initRegistryImages from '~/registry';
document.addEventListener('DOMContentLoaded', initRegistryImages);
Loading
Loading
@@ -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: {
Loading
Loading
@@ -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,
Loading
Loading
@@ -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) {
Loading
Loading
@@ -129,7 +112,7 @@ export default {
}
},
methods: {
...mapActions(['setMainEndpoint', 'fetchRepos']),
...mapActions(['setMainEndpoint', 'fetchRepos', 'setIsDeleteDisabled']),
},
};
</script>
Loading
Loading
@@ -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>
<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';
Loading
Loading
@@ -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']),
Loading
Loading
@@ -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')"
Loading
Loading
@@ -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.') }}
Loading
Loading
<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>
<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>
<script>
import { mapActions } from 'vuex';
import { mapActions, mapGetters } from 'vuex';
import {
GlButton,
GlFormCheckbox,
Loading
Loading
@@ -35,6 +35,11 @@ export default {
type: Object,
required: true,
},
canDeleteRepo: {
type: Boolean,
default: false,
required: false,
},
},
data() {
return {
Loading
Loading
@@ -45,6 +50,7 @@ export default {
};
},
computed: {
...mapGetters(['isDeleteDisabled']),
bulkDeletePath() {
return this.repo.tagsPath ? this.repo.tagsPath.replace('?format=json', '/bulk_destroy') : '';
},
Loading
Loading
@@ -165,6 +171,9 @@ export default {
}
}
},
canDeleteRow(item) {
return item && item.canDelete && !this.isDeleteDisabled;
},
},
};
</script>
Loading
Loading
@@ -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"
Loading
Loading
@@ -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"
Loading
Loading
@@ -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)"
Loading
Loading
@@ -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')"
Loading
Loading
Loading
Loading
@@ -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,
},
});
},
Loading
Loading
Loading
Loading
@@ -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 => {
Loading
Loading
@@ -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
Loading
Loading
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 () => {};
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';
Loading
Loading
Loading
Loading
@@ -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 => ({
Loading
Loading
@@ -17,6 +21,7 @@ export default {
location: el.location,
name: el.path,
tagsPath: el.tags_path,
projectId: el.project_id,
})),
});
},
Loading
Loading
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:
* {
Loading
Loading
Loading
Loading
@@ -13,7 +13,7 @@ module Boards
end
 
def board_parent
@board_parent ||= board.parent
@board_parent ||= board.resource_parent
end
 
def record_not_found(exception)
Loading
Loading
Loading
Loading
@@ -9,7 +9,7 @@ module Boards
skip_before_action :authenticate_user!, only: [:index]
 
def index
lists = Boards::Lists::ListService.new(board.parent, current_user).execute(board)
lists = Boards::Lists::ListService.new(board.resource_parent, current_user).execute(board)
 
List.preload_preferences_for_user(lists, current_user)
 
Loading
Loading
@@ -17,7 +17,7 @@ module Boards
end
 
def create
list = Boards::Lists::CreateService.new(board.parent, current_user, create_list_params).execute(board)
list = Boards::Lists::CreateService.new(board.resource_parent, current_user, create_list_params).execute(board)
 
if list.valid?
render json: serialize_as_json(list)
Loading
Loading
Loading
Loading
@@ -35,7 +35,7 @@ module MilestoneActions
 
render json: tabs_json("shared/milestones/_labels_tab", {
labels: milestone_labels.map do |label|
label.present(issuable_subject: @milestone.parent)
label.present(issuable_subject: @milestone.resource_parent)
end
})
end
Loading
Loading
Loading
Loading
@@ -44,7 +44,7 @@ class Groups::MilestonesController < Groups::ApplicationController
# all projects milestones states at once.
milestones, update_params = get_milestones_for_update
milestones.each do |milestone|
Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone)
Milestones::UpdateService.new(milestone.resource_parent, current_user, update_params).execute(milestone)
end
 
redirect_to milestone_path
Loading
Loading
# frozen_string_literal: true
module Groups
module Registry
class RepositoriesController < Groups::ApplicationController
before_action :verify_container_registry_enabled!
before_action :authorize_read_container_image!
def index
track_event(:list_repositories)
respond_to do |format|
format.html
format.json do
@images = group.container_repositories.with_api_entity_associations
render json: ContainerRepositoriesSerializer
.new(current_user: current_user)
.represent(@images)
end
end
end
private
def verify_container_registry_enabled!
render_404 unless Gitlab.config.registry.enabled
end
def authorize_read_container_image!
return render_404 unless can?(current_user, :read_container_image, group)
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment