diff options
author | Clement Ho <ClemMakesApps@gmail.com> | 2018-05-16 12:46:52 -0500 |
---|---|---|
committer | Clement Ho <ClemMakesApps@gmail.com> | 2018-05-16 12:46:52 -0500 |
commit | fcae43c604cb903c11a2cf6c27824e47f315831a (patch) | |
tree | aeefc767a1bdccef5ef77e950c16f5deabac4691 /app | |
parent | e32aed55b8cf01965558056d86087a31f65ca874 (diff) | |
parent | 9e41ed61ea70532578416e01c228ff7a81abc192 (diff) | |
download | gitlab-ce-fcae43c604cb903c11a2cf6c27824e47f315831a.tar.gz |
Merge branch 'master' into bootstrap4
Diffstat (limited to 'app')
43 files changed, 500 insertions, 415 deletions
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index c2a35341eb2..fae580c091b 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -179,7 +179,7 @@ role="row" > <div - class="alert alert-danger alert-block append-bottom-0 table-section section-100" + class="alert alert-danger alert-block append-bottom-0" role="gridcell" > <div> diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue index 4ddca918495..2dc39d1a186 100644 --- a/app/assets/javascripts/notes/components/note_edited_text.vue +++ b/app/assets/javascripts/notes/components/note_edited_text.vue @@ -32,17 +32,17 @@ export default { <template> <div :class="className"> {{ actionText }} - <time-ago-tooltip - :time="editedAt" - tooltip-placement="bottom" - /> <template v-if="editedBy"> - by + {{ s__('ByAuthor|by') }} <a :href="editedBy.path" class="js-vue-author author_link"> {{ editedBy.name }} </a> </template> + <time-ago-tooltip + :time="editedAt" + tooltip-placement="bottom" + /> </div> </template> diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue index c3d1ef1fcc6..7183d0b50b2 100644 --- a/app/assets/javascripts/notes/components/note_header.vue +++ b/app/assets/javascripts/notes/components/note_header.vue @@ -62,6 +62,21 @@ export default { <template> <div class="note-header-info"> + <div + v-if="includeToggle" + class="discussion-actions"> + <button + @click="handleToggle" + class="note-action-button discussion-toggle-button js-vue-toggle-button" + type="button"> + <i + :class="toggleChevronClass" + class="fa" + aria-hidden="true"> + </i> + {{ __('Toggle discussion') }} + </button> + </div> <a :href="author.path"> <span class="note-header-author-name">{{ author.name }}</span> <span class="note-headline-light"> @@ -95,20 +110,5 @@ export default { </i> </span> </span> - <div - v-if="includeToggle" - class="discussion-actions"> - <button - @click="handleToggle" - class="note-action-button discussion-toggle-button js-vue-toggle-button" - type="button"> - <i - :class="toggleChevronClass" - class="fa" - aria-hidden="true"> - </i> - Toggle discussion - </button> - </div> </div> </template> diff --git a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue new file mode 100644 index 00000000000..df21e2f8771 --- /dev/null +++ b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue @@ -0,0 +1,77 @@ +<script> +import _ from 'underscore'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; +import { s__, sprintf } from '~/locale'; + +export default { + components: { + GlModal, + }, + props: { + deleteWikiUrl: { + type: String, + required: true, + default: '', + }, + pageTitle: { + type: String, + required: true, + default: '', + }, + csrfToken: { + type: String, + required: true, + default: '', + }, + }, + computed: { + message() { + return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?'); + }, + title() { + return sprintf( + s__('WikiPageConfirmDelete|Delete page %{pageTitle}?'), + { + pageTitle: _.escape(this.pageTitle), + }, + false, + ); + }, + }, + methods: { + onSubmit() { + this.$refs.form.submit(); + }, + }, +}; +</script> + +<template> + <gl-modal + id="delete-wiki-modal" + :header-title-text="title" + footer-primary-button-variant="danger" + :footer-primary-button-text="s__('WikiPageConfirmDelete|Delete page')" + @submit="onSubmit" + > + {{ message }} + <form + ref="form" + :action="deleteWikiUrl" + method="post" + class="form-horizontal js-requires-input" + > + <input + ref="method" + type="hidden" + name="_method" + value="delete" + /> + <input + type="hidden" + name="authenticity_token" + :value="csrfToken" + /> + </form> + </gl-modal> +</template> diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js index ec01c66ffda..0295653cb29 100644 --- a/app/assets/javascripts/pages/projects/wikis/index.js +++ b/app/assets/javascripts/pages/projects/wikis/index.js @@ -1,12 +1,40 @@ import $ from 'jquery'; +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; +import csrf from '~/lib/utils/csrf'; import Wikis from './wikis'; import ShortcutsWiki from '../../../shortcuts_wiki'; import ZenMode from '../../../zen_mode'; import GLForm from '../../../gl_form'; +import deleteWikiModal from './components/delete_wiki_modal.vue'; document.addEventListener('DOMContentLoaded', () => { new Wikis(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new new GLForm($('.wiki-form'), true); // eslint-disable-line no-new + + const deleteWikiButton = document.getElementById('delete-wiki-button'); + + if (deleteWikiButton) { + Vue.use(Translate); + + const { deleteWikiUrl, pageTitle } = deleteWikiButton.dataset; + const deleteWikiModalEl = document.getElementById('delete-wiki-modal'); + const deleteModal = new Vue({ // eslint-disable-line + el: deleteWikiModalEl, + data: { + deleteWikiUrl: '', + }, + render(createElement) { + return createElement(deleteWikiModal, { + props: { + pageTitle, + deleteWikiUrl, + csrfToken: csrf.token, + }, + }); + }, + }); + } }); diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue deleted file mode 100644 index 0cdffbde05b..00000000000 --- a/app/assets/javascripts/pipelines/components/async_button.vue +++ /dev/null @@ -1,95 +0,0 @@ -<script> - /* eslint-disable no-alert */ - - import eventHub from '../event_hub'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; - import icon from '../../vue_shared/components/icon.vue'; - import tooltip from '../../vue_shared/directives/tooltip'; - - export default { - directives: { - tooltip, - }, - components: { - loadingIcon, - icon, - }, - props: { - endpoint: { - type: String, - required: true, - }, - title: { - type: String, - required: true, - }, - icon: { - type: String, - required: true, - }, - cssClass: { - type: String, - required: true, - }, - pipelineId: { - type: Number, - required: true, - }, - type: { - type: String, - required: true, - }, - }, - data() { - return { - isLoading: false, - }; - }, - computed: { - buttonClass() { - return `btn ${this.cssClass}`; - }, - }, - created() { - // We're using eventHub to listen to the modal here instead of - // using props because it would would make the parent components - // much more complex to keep track of the loading state of each button - eventHub.$on('postAction', this.setLoading); - }, - beforeDestroy() { - eventHub.$off('postAction', this.setLoading); - }, - methods: { - onClick() { - eventHub.$emit('openConfirmationModal', { - pipelineId: this.pipelineId, - endpoint: this.endpoint, - type: this.type, - }); - }, - setLoading(endpoint) { - if (endpoint === this.endpoint) { - this.isLoading = true; - } - }, - }, - }; -</script> - -<template> - <button - v-tooltip - type="button" - @click="onClick" - :class="buttonClass" - :title="title" - :aria-label="title" - data-container="body" - data-placement="top" - :disabled="isLoading"> - <icon - :name="icon" - /> - <loading-icon v-if="isLoading" /> - </button> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index 714aed1333e..41986b827cd 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -1,7 +1,7 @@ <script> - import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; + import Modal from '~/vue_shared/components/gl_modal.vue'; import { s__, sprintf } from '~/locale'; - import pipelinesTableRowComponent from './pipelines_table_row.vue'; + import PipelinesTableRowComponent from './pipelines_table_row.vue'; import eventHub from '../event_hub'; /** @@ -11,8 +11,8 @@ */ export default { components: { - pipelinesTableRowComponent, - DeprecatedModal, + PipelinesTableRowComponent, + Modal, }, props: { pipelines: { @@ -37,30 +37,18 @@ return { pipelineId: '', endpoint: '', - type: '', }; }, computed: { modalTitle() { - return this.type === 'stop' ? - sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), { - pipelineId: `'${this.pipelineId}'`, - }, false) : - sprintf(s__('Pipeline|Retry pipeline #%{pipelineId}?'), { - pipelineId: `'${this.pipelineId}'`, - }, false); + return sprintf(s__('Pipeline|Stop pipeline #%{pipelineId}?'), { + pipelineId: `${this.pipelineId}`, + }, false); }, modalText() { - return this.type === 'stop' ? - sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), { - pipelineId: `<strong>#${this.pipelineId}</strong>`, - }, false) : - sprintf(s__('Pipeline|You’re about to retry pipeline %{pipelineId}.'), { - pipelineId: `<strong>#${this.pipelineId}</strong>`, - }, false); - }, - primaryButtonLabel() { - return this.type === 'stop' ? s__('Pipeline|Stop pipeline') : s__('Pipeline|Retry pipeline'); + return sprintf(s__('Pipeline|You’re about to stop pipeline %{pipelineId}.'), { + pipelineId: `<strong>#${this.pipelineId}</strong>`, + }, false); }, }, created() { @@ -73,7 +61,6 @@ setModalData(data) { this.pipelineId = data.pipelineId; this.endpoint = data.endpoint; - this.type = data.type; }, onSubmit() { eventHub.$emit('postAction', this.endpoint); @@ -120,20 +107,16 @@ :auto-devops-help-path="autoDevopsHelpPath" :view-type="viewType" /> - <deprecated-modal + + <modal id="confirmation-modal" - :title="modalTitle" - :text="modalText" - kind="danger" - :primary-button-label="primaryButtonLabel" + :header-title-text="modalTitle" + footer-primary-button-variant="danger" + :footer-primary-button-text="s__('Pipeline|Stop pipeline')" @submit="onSubmit" > - <template - slot="body" - slot-scope="props" - > - <p v-html="props.text"></p> - </template> - </deprecated-modal> + <span v-html="modalText"></span> + </modal> + </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 0dcda8a2234..eaff0b25354 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -1,13 +1,14 @@ <script> - /* eslint-disable no-param-reassign */ - import asyncButtonComponent from './async_button.vue'; - import pipelinesActionsComponent from './pipelines_actions.vue'; - import pipelinesArtifactsComponent from './pipelines_artifacts.vue'; - import ciBadge from '../../vue_shared/components/ci_badge_link.vue'; - import pipelineStage from './stage.vue'; - import pipelineUrl from './pipeline_url.vue'; - import pipelinesTimeago from './time_ago.vue'; - import commitComponent from '../../vue_shared/components/commit.vue'; + import eventHub from '../event_hub'; + import PipelinesActionsComponent from './pipelines_actions.vue'; + import PipelinesArtifactsComponent from './pipelines_artifacts.vue'; + import CiBadge from '../../vue_shared/components/ci_badge_link.vue'; + import PipelineStage from './stage.vue'; + import PipelineUrl from './pipeline_url.vue'; + import PipelinesTimeago from './time_ago.vue'; + import CommitComponent from '../../vue_shared/components/commit.vue'; + import LoadingButton from '../../vue_shared/components/loading_button.vue'; + import Icon from '../../vue_shared/components/icon.vue'; /** * Pipeline table row. @@ -16,14 +17,15 @@ */ export default { components: { - asyncButtonComponent, - pipelinesActionsComponent, - pipelinesArtifactsComponent, - commitComponent, - pipelineStage, - pipelineUrl, - ciBadge, - pipelinesTimeago, + PipelinesActionsComponent, + PipelinesArtifactsComponent, + CommitComponent, + PipelineStage, + PipelineUrl, + CiBadge, + PipelinesTimeago, + LoadingButton, + Icon, }, props: { pipeline: { @@ -44,6 +46,12 @@ required: true, }, }, + data() { + return { + isRetrying: false, + isCancelling: false, + }; + }, computed: { /** * If provided, returns the commit tag. @@ -119,8 +127,10 @@ if (this.pipeline.ref) { return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => { if (prop === 'path') { + // eslint-disable-next-line no-param-reassign accumulator.ref_url = this.pipeline.ref[prop]; } else { + // eslint-disable-next-line no-param-reassign accumulator[prop] = this.pipeline.ref[prop]; } return accumulator; @@ -216,6 +226,21 @@ return this.viewType === 'child'; }, }, + + methods: { + handleCancelClick() { + this.isCancelling = true; + + eventHub.$emit('openConfirmationModal', { + pipelineId: this.pipeline.id, + endpoint: this.pipeline.cancel_path, + }); + }, + handleRetryClick() { + this.isRetrying = true; + eventHub.$emit('retryPipeline', this.pipeline.retry_path); + }, + }, }; </script> <template> @@ -287,7 +312,8 @@ <div v-if="displayPipelineActions" - class="table-section section-20 table-button-footer pipeline-actions"> + class="table-section section-20 table-button-footer pipeline-actions" + > <div class="btn-group table-action-buttons"> <pipelines-actions-component v-if="pipeline.details.manual_actions.length" @@ -300,29 +326,27 @@ :artifacts="pipeline.details.artifacts" /> - <async-button-component + <loading-button v-if="pipeline.flags.retryable" - :endpoint="pipeline.retry_path" - css-class="js-pipelines-retry-button btn-default btn-retry" - title="Retry" - icon="repeat" - :pipeline-id="pipeline.id" - data-toggle="modal" - data-target="#confirmation-modal" - type="retry" - /> + @click="handleRetryClick" + container-class="js-pipelines-retry-button btn btn-default btn-retry" + :loading="isRetrying" + :disabled="isRetrying" + > + <icon name="repeat" /> + </loading-button> - <async-button-component + <loading-button v-if="pipeline.flags.cancelable" - :endpoint="pipeline.cancel_path" - css-class="js-pipelines-cancel-button btn-remove" - title="Stop" - icon="close" - :pipeline-id="pipeline.id" + @click="handleCancelClick" data-toggle="modal" data-target="#confirmation-modal" - type="stop" - /> + container-class="js-pipelines-cancel-button btn btn-remove" + :loading="isCancelling" + :disabled="isCancelling" + > + <icon name="close" /> + </loading-button> </div> </div> </div> diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index 6d87f75ae8e..de0faf181e5 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -53,10 +53,12 @@ export default { }); eventHub.$on('postAction', this.postAction); + eventHub.$on('retryPipeline', this.postAction); eventHub.$on('clickedDropdown', this.updateTable); }, beforeDestroy() { eventHub.$off('postAction', this.postAction); + eventHub.$off('retryPipeline', this.postAction); eventHub.$off('clickedDropdown', this.updateTable); }, destroyed() { diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index fc65f1a714e..2fc3778820b 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -28,11 +28,6 @@ isOpen: false, }; }, - computed: { - clipboardText() { - return `docker pull ${this.repo.location}`; - }, - }, methods: { ...mapActions([ 'fetchRepos', @@ -84,7 +79,7 @@ <clipboard-button v-if="repo.location" - :text="clipboardText" + :text="repo.location" :title="repo.location" css-class="btn-default btn-transparent btn-clipboard" /> diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index 36215a6f845..e4a4b3bb129 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -56,10 +56,6 @@ .catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY)); }, - clipboardText(text) { - return `docker pull ${text}`; - }, - showError(message) { Flash(errorMessages[message]); }, @@ -89,7 +85,7 @@ <clipboard-button v-if="item.location" :title="item.location" - :text="clipboardText(item.location)" + :text="item.location" css-class="btn-default btn-transparent btn-clipboard" /> </td> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue deleted file mode 100644 index f0298f732ea..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_maintainer_edit.vue +++ /dev/null @@ -1,20 +0,0 @@ -<script> - export default { - name: 'MRWidgetMaintainerEdit', - props: { - maintainerEditAllowed: { - type: Boolean, - default: false, - required: false, - }, - }, - }; -</script> - -<template> - <section class="mr-info-list mr-links"> - <p v-if="maintainerEditAllowed"> - {{ s__("mrWidget|Allows edits from maintainers") }} - </p> - </section> -</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue index bf8628d18a6..926a3172412 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue @@ -10,6 +10,6 @@ In EE, the configuration extends this object to add a functioning squash-before- button. */ -export default { - template: '', -}; +<script> + export default {}; +</script> diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 7f5f28091da..15097fa2a3f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -15,7 +15,6 @@ export { default as WidgetHeader } from './components/mr_widget_header.vue'; export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue'; export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue'; export { default as Deployment } from './components/deployment.vue'; -export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue'; export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue'; export { default as MergedState } from './components/states/mr_widget_merged.vue'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue'; @@ -41,8 +40,8 @@ export { default as MRWidgetService } from './services/mr_widget_service'; export { default as eventHub } from './event_hub'; export { default as getStateKey } from './stores/get_state_key'; export { default as stateMaps } from './stores/state_maps'; -export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge'; +export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge.vue'; export { default as notify } from '../lib/utils/notify'; export { default as SourceBranchRemovalStatus } from './components/source_branch_removal_status.vue'; -export { default as mrWidgetOptions } from './mr_widget_options'; +export { default as mrWidgetOptions } from './mr_widget_options.vue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 345f9ac1b4b..f69fe03fcb3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -1,12 +1,13 @@ +<script> + import Project from '~/pages/projects/project'; import SmartInterval from '~/smart_interval'; -import Flash from '../flash'; +import createFlash from '../flash'; import { WidgetHeader, WidgetMergeHelp, WidgetPipeline, Deployment, - WidgetMaintainerEdit, WidgetRelatedLinks, MergedState, ClosedState, @@ -40,10 +41,39 @@ import { setFavicon } from '../lib/utils/common_utils'; export default { el: '#js-vue-mr-widget', name: 'MRWidget', + components: { + 'mr-widget-header': WidgetHeader, + 'mr-widget-merge-help': WidgetMergeHelp, + 'mr-widget-pipeline': WidgetPipeline, + Deployment, + 'mr-widget-related-links': WidgetRelatedLinks, + 'mr-widget-merged': MergedState, + 'mr-widget-closed': ClosedState, + 'mr-widget-merging': MergingState, + 'mr-widget-failed-to-merge': FailedToMerge, + 'mr-widget-wip': WorkInProgressState, + 'mr-widget-archived': ArchivedState, + 'mr-widget-conflicts': ConflictsState, + 'mr-widget-nothing-to-merge': NothingToMergeState, + 'mr-widget-not-allowed': NotAllowedState, + 'mr-widget-missing-branch': MissingBranchState, + 'mr-widget-ready-to-merge': ReadyToMergeState, + 'sha-mismatch': ShaMismatchState, + 'mr-widget-squash-before-merge': SquashBeforeMerge, + 'mr-widget-checking': CheckingState, + 'mr-widget-unresolved-discussions': UnresolvedDiscussionsState, + 'mr-widget-pipeline-blocked': PipelineBlockedState, + 'mr-widget-pipeline-failed': PipelineFailedState, + 'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState, + 'mr-widget-auto-merge-failed': AutoMergeFailed, + 'mr-widget-rebase': RebaseState, + SourceBranchRemovalStatus, + }, props: { mrData: { type: Object, required: false, + default: null, }, }, data() { @@ -72,6 +102,13 @@ export default { (!this.mr.isNothingToMergeState && !this.mr.isMergedState); }, }, + created() { + this.initPolling(); + this.bindEventHubListeners(); + }, + mounted() { + this.handleMounted(); + }, methods: { createService(store) { const endpoints = { @@ -99,7 +136,7 @@ export default { cb.call(null, data); } }) - .catch(() => new Flash('Something went wrong. Please try again.')); + .catch(() => createFlash('Something went wrong. Please try again.')); }, initPolling() { this.pollingInterval = new SmartInterval({ @@ -134,7 +171,7 @@ export default { } }) .catch(() => { - new Flash('Something went wrong while fetching the environments for this merge request. Please try again.'); // eslint-disable-line + createFlash('Something went wrong while fetching the environments for this merge request. Please try again.'); // eslint-disable-line }); }, fetchActionsContent() { @@ -147,7 +184,7 @@ export default { Project.initRefSwitcher(); } }) - .catch(() => new Flash('Something went wrong. Please try again.')); + .catch(() => createFlash('Something went wrong. Please try again.')); }, handleNotification(data) { if (data.ci_status === this.mr.ciStatus) return; @@ -202,76 +239,53 @@ export default { this.initDeploymentsPolling(); }, }, - created() { - this.initPolling(); - this.bindEventHubListeners(); - }, - mounted() { - this.handleMounted(); - }, - components: { - 'mr-widget-header': WidgetHeader, - 'mr-widget-merge-help': WidgetMergeHelp, - 'mr-widget-pipeline': WidgetPipeline, - Deployment, - 'mr-widget-maintainer-edit': WidgetMaintainerEdit, - 'mr-widget-related-links': WidgetRelatedLinks, - 'mr-widget-merged': MergedState, - 'mr-widget-closed': ClosedState, - 'mr-widget-merging': MergingState, - 'mr-widget-failed-to-merge': FailedToMerge, - 'mr-widget-wip': WorkInProgressState, - 'mr-widget-archived': ArchivedState, - 'mr-widget-conflicts': ConflictsState, - 'mr-widget-nothing-to-merge': NothingToMergeState, - 'mr-widget-not-allowed': NotAllowedState, - 'mr-widget-missing-branch': MissingBranchState, - 'mr-widget-ready-to-merge': ReadyToMergeState, - 'mr-widget-sha-mismatch': ShaMismatchState, - 'mr-widget-squash-before-merge': SquashBeforeMerge, - 'mr-widget-checking': CheckingState, - 'mr-widget-unresolved-discussions': UnresolvedDiscussionsState, - 'mr-widget-pipeline-blocked': PipelineBlockedState, - 'mr-widget-pipeline-failed': PipelineFailedState, - 'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState, - 'mr-widget-auto-merge-failed': AutoMergeFailed, - 'mr-widget-rebase': RebaseState, - SourceBranchRemovalStatus, - }, - template: ` - <div class="mr-state-widget prepend-top-default"> - <mr-widget-header :mr="mr" /> - <mr-widget-pipeline - v-if="shouldRenderPipelines" - :pipeline="mr.pipeline" - :ci-status="mr.ciStatus" - :has-ci="mr.hasCI" - /> - <deployment - v-for="deployment in mr.deployments" - :key="deployment.id" - :deployment="deployment" +}; +</script> +<template> + <div class="mr-state-widget prepend-top-default"> + <mr-widget-header + :mr="mr" + /> + <mr-widget-pipeline + v-if="shouldRenderPipelines" + :pipeline="mr.pipeline" + :ci-status="mr.ciStatus" + :has-ci="mr.hasCI" + /> + <deployment + v-for="deployment in mr.deployments" + :key="deployment.id" + :deployment="deployment" + /> + <div class="mr-widget-section"> + <component + :is="componentName" + :mr="mr" + :service="service" + /> + + <section + v-if="mr.maintainerEditAllowed" + class="mr-info-list mr-links" + > + {{ s__("mrWidget|Allows edits from maintainers") }} + </section> + + <mr-widget-related-links + v-if="shouldRenderRelatedLinks" + :state="mr.state" + :related-links="mr.relatedLinks" + /> + + <source-branch-removal-status + v-if="shouldRenderSourceBranchRemovalStatus" /> - <div class="mr-widget-section"> - <component - :is="componentName" - :mr="mr" - :service="service" /> - <mr-widget-maintainer-edit - :maintainerEditAllowed="mr.maintainerEditAllowed" /> - <mr-widget-related-links - v-if="shouldRenderRelatedLinks" - :state="mr.state" - :related-links="mr.relatedLinks" /> - <source-branch-removal-status - v-if="shouldRenderSourceBranchRemovalStatus" - /> - </div> - <div - class="mr-widget-footer" - v-if="shouldRenderMergeHelp"> - <mr-widget-merge-help /> - </div> </div> - `, -}; + <div + class="mr-widget-footer" + v-if="shouldRenderMergeHelp" + > + <mr-widget-merge-help /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue index e832d94d32f..88c13a1f340 100644 --- a/app/assets/javascripts/vue_shared/components/loading_button.vue +++ b/app/assets/javascripts/vue_shared/components/loading_button.vue @@ -70,12 +70,14 @@ /> </transition> <transition name="fade"> - <span - v-if="label" - class="js-loading-button-label" - > - {{ label }} - </span> + <slot> + <span + v-if="label" + class="js-loading-button-label" + > + {{ label }} + </span> + </slot> </transition> </button> </template> diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 9ff24ebc127..0ea0b65b95f 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -210,3 +210,15 @@ margin-left: -$size; } } + +/* + * Mixin that fixes wrapping issues with long strings (e.g. URLs) + * + * Note: the width needs to be set for it to work in Firefox + */ +@mixin overflow-break-word { + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; + max-width: 100%; +} diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss index bb185d2bd81..f80e9b1014b 100644 --- a/app/assets/stylesheets/framework/snippets.scss +++ b/app/assets/stylesheets/framework/snippets.scss @@ -67,7 +67,8 @@ padding: 8px 40px; } - .embed-toggle { + .embed-toggle, + .snippet-clipboard-btn { height: 35px; } } diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index cd47f34d565..135cc5de0a5 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -284,6 +284,9 @@ box-shadow: 0 1px 2px $issue-boards-card-shadow; list-style: none; + // as a fallback, hide overflow content so that dragging and dropping still works + overflow: hidden; + &:not(:last-child) { margin-bottom: 5px; } @@ -310,14 +313,13 @@ } .board-card-title { + @include overflow-break-word(); margin: 0 30px 0 0; font-size: 1em; line-height: inherit; a { color: $gl-text-color; - word-wrap: break-word; - word-break: break-word; margin-right: 2px; } } @@ -462,8 +464,8 @@ } .issuable-header-text { + @include overflow-break-word(); padding-right: 35px; - word-break: break-word; > strong { font-weight: $gl-font-weight-bold; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index f568c5d7036..d865dbc828f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -407,10 +407,6 @@ ul.notes { .note-header { display: flex; justify-content: space-between; - - @include notes-media('max', map-get($grid-breakpoints, sm) - 1) { - flex-flow: row wrap; - } } .note-header-info { @@ -473,11 +469,6 @@ ul.notes { margin-left: 10px; color: $gray-darkest; - @include notes-media('max', map-get($grid-breakpoints, lg) - 1) { - float: none; - margin-left: 0; - } - .btn-group > .discussion-next-btn { margin-left: -1px; } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2caffec66ac..2843d70c645 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -13,8 +13,7 @@ class ApplicationController < ActionController::Base before_action :authenticate_sessionless_user! before_action :authenticate_user! - before_action :enforce_terms!, if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms }, - unless: :peek_request? + before_action :enforce_terms!, if: :should_enforce_terms? before_action :validate_user_service_ticket! before_action :check_password_expiration before_action :ldap_security_check @@ -373,4 +372,10 @@ class ApplicationController < ActionController::Base def peek_request? request.path.start_with?('/-/peek') end + + def should_enforce_terms? + return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms + + !(peek_request? || devise_controller?) + end end diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb index 55011c89886..237c93daee8 100644 --- a/app/controllers/concerns/send_file_upload.rb +++ b/app/controllers/concerns/send_file_upload.rb @@ -2,6 +2,10 @@ module SendFileUpload def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment') if attachment redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}" } + # By default, Rails will send uploads with an extension of .js with a + # content-type of text/javascript, which will trigger Rails' + # cross-origin JavaScript protection. + send_params[:content_type] = 'text/plain' if File.extname(attachment) == '.js' send_params.merge!(filename: attachment, disposition: disposition) end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index bc13b8ad7ba..4d4c2af2415 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -8,19 +8,6 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_create_note!, only: [:create] before_action :authorize_resolve_note!, only: [:resolve, :unresolve] - # - # This is a fix to make spinach feature tests passing: - # Controller actions are returned from AbstractController::Base and methods of parent classes are - # excluded in order to return only specific controller related methods. - # That is ok for the app (no :create method in ancestors) - # but fails for tests because there is a :create method on FactoryBot (one of the ancestors) - # - # see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78 - # - def create - super - end - def delete_attachment note.remove_attachment! note.update_attribute(:attachment, nil) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 0b1b46944aa..f7417a6a5aa 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -181,4 +181,8 @@ class Projects::PipelinesController < Projects::ApplicationController # Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343 Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339') end + + def authorize_update_pipeline! + return access_denied! unless can?(current_user, :update_pipeline, @pipeline) + end end diff --git a/app/controllers/users/terms_controller.rb b/app/controllers/users/terms_controller.rb index 95c5c3432d5..ab685b9106e 100644 --- a/app/controllers/users/terms_controller.rb +++ b/app/controllers/users/terms_controller.rb @@ -3,6 +3,10 @@ module Users include InternalRedirect skip_before_action :enforce_terms! + skip_before_action :check_password_expiration + skip_before_action :check_two_factor_requirement + skip_before_action :require_email + before_action :terms layout 'terms' diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb index 16451993e93..7b076728685 100644 --- a/app/helpers/auto_devops_helper.rb +++ b/app/helpers/auto_devops_helper.rb @@ -24,6 +24,15 @@ module AutoDevopsHelper end end + def cluster_ingress_ip(project) + project + .cluster_ingresses + .where("external_ip is not null") + .limit(1) + .pluck(:external_ip) + .first + end + private def missing_auto_devops_domain?(project) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 079b3cd3aa0..cb6f709c604 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -41,7 +41,7 @@ module EventsHelper key = key.to_s active = 'active' if @event_filter.active?(key) link_opts = { - class: "event-filter-link has-tooltip", + class: "event-filter-link", id: "#{key}_event_filter", title: tooltip } diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index eb81dc2de43..fa54eafd3a3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -257,6 +257,7 @@ module ProjectsHelper if project.builds_enabled? && can?(current_user, :read_pipeline, project) nav_tabs << :pipelines + nav_tabs << :operations end if project.external_issue_tracker diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index e803cd3a8d8..ce9373f5883 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -42,22 +42,11 @@ module UsersHelper items << :sign_out if current_user - # TODO: Remove these conditions when the permissions are prevented in - # https://gitlab.com/gitlab-org/gitlab-ce/issues/45849 - terms_not_enforced = !Gitlab::CurrentSettings - .current_application_settings - .enforce_terms? - required_terms_accepted = terms_not_enforced || current_user.terms_accepted? + return items if current_user&.required_terms_not_accepted? - items << :help if required_terms_accepted - - if can?(current_user, :read_user, current_user) && required_terms_accepted - items << :profile - end - - if can?(current_user, :update_user, current_user) && required_terms_accepted - items << :settings - end + items << :help + items << :profile if can?(current_user, :read_user, current_user) + items << :settings if can?(current_user, :update_user, current_user) items end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 0b90834d415..1f49764e7cc 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -37,12 +37,16 @@ module Ci delegate :id, to: :project, prefix: true delegate :full_path, to: :project, prefix: true - validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create validates :sha, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? } validates :status, presence: { unless: :importing? } validate :valid_commit_sha, unless: :importing? + # Replace validator below with + # `validates :source, presence: { unless: :importing? }, on: :create` + # when removing Gitlab.rails5? code. + validate :valid_source, unless: :importing?, on: :create + after_create :keep_around_commits, unless: :importing? enum source: { @@ -601,5 +605,11 @@ module Ci project.repository.keep_around(self.sha) project.repository.keep_around(self.before_sha) end + + def valid_source + if source.nil? || source == "unknown" + errors.add(:source, "invalid source") + end + end end end diff --git a/app/models/list.rb b/app/models/list.rb index 918275be142..5daf35ef845 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -31,7 +31,8 @@ class List < ActiveRecord::Base if options.key?(:label) json[:label] = label.as_json( project: board.project, - only: [:id, :title, :description, :color] + only: [:id, :title, :description, :color], + methods: [:text_color] ) end end diff --git a/app/models/project.rb b/app/models/project.rb index 32d34f5e9b8..534a0e630af 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -217,6 +217,7 @@ class Project < ActiveRecord::Base has_one :cluster_project, class_name: 'Clusters::Project' has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster' + has_many :cluster_ingresses, through: :clusters, source: :application_ingress, class_name: 'Clusters::Applications::Ingress' # Container repositories need to remove data from the container registry, # which is not managed by the DB. Hence we're still using dependent: :destroy diff --git a/app/models/user.rb b/app/models/user.rb index d74d5aade5a..173ab38e20c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1097,8 +1097,11 @@ class User < ActiveRecord::Base # <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92> # def increment_failed_attempts! + return if ::Gitlab::Database.read_only? + self.failed_attempts ||= 0 self.failed_attempts += 1 + if attempts_exceeded? lock_access! unless access_locked? else @@ -1197,6 +1200,11 @@ class User < ActiveRecord::Base accepted_term_id.present? end + def required_terms_not_accepted? + Gitlab::CurrentSettings.current_application_settings.enforce_terms? && + !terms_accepted? + end + protected # override, from Devise::Validatable diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index 808a81cbbf9..8b65758f3e8 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -14,11 +14,20 @@ module Ci @subject.triggered_by?(@user) end + condition(:branch_allows_maintainer_push) do + @subject.project.branch_allows_maintainer_push?(@user, @subject.ref) + end + rule { protected_ref }.policy do prevent :update_build prevent :erase_build end rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build + + rule { can?(:public_access) & branch_allows_maintainer_push }.policy do + enable :update_build + enable :update_commit_status + end end end diff --git a/app/policies/ci/pipeline_policy.rb b/app/policies/ci/pipeline_policy.rb index 6363c382ff8..540e4235299 100644 --- a/app/policies/ci/pipeline_policy.rb +++ b/app/policies/ci/pipeline_policy.rb @@ -4,8 +4,16 @@ module Ci condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) } + condition(:branch_allows_maintainer_push) do + @subject.project.branch_allows_maintainer_push?(@user, @subject.ref) + end + rule { protected_ref }.prevent :update_pipeline + rule { can?(:public_access) & branch_allows_maintainer_push }.policy do + enable :update_pipeline + end + def ref_protected?(user, project, tag, ref) access = ::Gitlab::UserAccess.new(user, project: project) diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 64e550d19d0..1cf5515d9d7 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -1,22 +1,24 @@ class GlobalPolicy < BasePolicy desc "User is blocked" with_options scope: :user, score: 0 - condition(:blocked) { @user.blocked? } + condition(:blocked) { @user&.blocked? } desc "User is an internal user" with_options scope: :user, score: 0 - condition(:internal) { @user.internal? } + condition(:internal) { @user&.internal? } desc "User's access has been locked" with_options scope: :user, score: 0 - condition(:access_locked) { @user.access_locked? } + condition(:access_locked) { @user&.access_locked? } - condition(:can_create_fork, scope: :user) { @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } } + condition(:can_create_fork, scope: :user) { @user && @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } } + + condition(:required_terms_not_accepted, scope: :user, score: 0) do + @user&.required_terms_not_accepted? + end rule { anonymous }.policy do prevent :log_in - prevent :access_api - prevent :access_git prevent :receive_notifications prevent :use_quick_actions prevent :create_group @@ -38,6 +40,11 @@ class GlobalPolicy < BasePolicy prevent :use_quick_actions end + rule { required_terms_not_accepted }.policy do + prevent :access_api + prevent :access_git + end + rule { can_create_group }.policy do enable :create_group end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 5759b1a376f..99a0d7118f2 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -76,7 +76,7 @@ class ProjectPolicy < BasePolicy condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled } desc "Has merge requests allowing pushes to user" - condition(:has_merge_requests_allowing_pushes, scope: :subject) do + condition(:has_merge_requests_allowing_pushes) do project.merge_requests_allowing_push_to_user(user).any? end @@ -354,9 +354,7 @@ class ProjectPolicy < BasePolicy # to run pipelines for the branches they have access to. rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do enable :create_build - enable :update_build enable :create_pipeline - enable :update_pipeline end rule do diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index 3e65d211c50..1765251c93d 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -13,7 +13,7 @@ = icon("chevron-up") - else = icon("chevron-down") - Toggle discussion + = _('Toggle discussion') = link_to_member(@project, discussion.author, avatar: false) .inline.discussion-headline-light diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 146c183da73..dae3f5d6518 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -13,13 +13,13 @@ .nav-icon-container = sprite_icon('project') %span.nav-item-name - Project + = _('Project') %ul.sidebar-sub-level-items = nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do = link_to project_path(@project) do %strong.fly-out-top-item-name - #{ _('Overview') } + = _('Overview') %li.divider.fly-out-top-item = nav_link(path: 'projects#show') do = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do @@ -40,45 +40,45 @@ .nav-icon-container = sprite_icon('doc_text') %span.nav-item-name - Repository + = _('Repository') %ul.sidebar-sub-level-items = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare projects/repositories tags branches releases graphs network), html_options: { class: "fly-out-top-item" } ) do = link_to project_tree_path(@project) do %strong.fly-out-top-item-name - #{ _('Repository') } + = _('Repository') %li.divider.fly-out-top-item = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = link_to project_tree_path(@project) do - #{ _('Files') } + = _('Files') = nav_link(controller: [:commit, :commits]) do = link_to project_commits_path(@project, current_ref) do - #{ _('Commits') } + = _('Commits') = nav_link(html_options: {class: branches_tab_class}) do = link_to project_branches_path(@project) do - #{ _('Branches') } + = _('Branches') = nav_link(controller: [:tags, :releases]) do = link_to project_tags_path(@project) do - #{ _('Tags') } + = _('Tags') = nav_link(path: 'graphs#show') do = link_to project_graph_path(@project, current_ref) do - #{ _('Contributors') } + = _('Contributors') = nav_link(controller: %w(network)) do = link_to project_network_path(@project, current_ref) do - #{ s_('ProjectNetworkGraph|Graph') } + = _('Graph') = nav_link(controller: :compare) do = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do - #{ _('Compare') } + = _('Compare') = nav_link(path: 'graphs#charts') do = link_to charts_project_graph_path(@project, current_ref) do - #{ _('Charts') } + = _('Charts') - if project_nav_tab? :issues = nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do @@ -86,7 +86,7 @@ .nav-icon-container = sprite_icon('issues') %span.nav-item-name - Issues + = _('Issues') - if @project.issues_enabled? %span.badge.badge-pill.count.issue_counter = number_with_delimiter(@project.open_issues_count) @@ -95,7 +95,7 @@ = nav_link(controller: :issues, html_options: { class: "fly-out-top-item" } ) do = link_to project_issues_path(@project) do %strong.fly-out-top-item-name - #{ _('Issues') } + = _('Issues') - if @project.issues_enabled? %span.badge.badge-pill.count.issue_counter.fly-out-badge = number_with_delimiter(@project.open_issues_count) @@ -103,7 +103,7 @@ = nav_link(controller: :issues, action: :index) do = link_to project_issues_path(@project), title: 'Issues' do %span - List + = _('List') = nav_link(controller: :boards) do = link_to project_boards_path(@project), title: boards_link_text do @@ -113,12 +113,12 @@ = nav_link(controller: :labels) do = link_to project_labels_path(@project), title: 'Labels' do %span - Labels + = _('Labels') = nav_link(controller: :milestones) do = link_to project_milestones_path(@project), title: 'Milestones' do %span - Milestones + = _('Milestones') - if project_nav_tab? :external_issue_tracker = nav_link do - issue_tracker = @project.external_issue_tracker @@ -139,54 +139,75 @@ .nav-icon-container = sprite_icon('git-merge') %span.nav-item-name - Merge Requests + = _('Merge Requests') %span.badge.badge-pill.count.merge_counter.js-merge-counter = number_with_delimiter(@project.open_merge_requests_count) %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(controller: :merge_requests, html_options: { class: "fly-out-top-item" } ) do = link_to project_merge_requests_path(@project) do %strong.fly-out-top-item-name - #{ _('Merge Requests') } + = _('Merge Requests') %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge = number_with_delimiter(@project.open_merge_requests_count) - if project_nav_tab? :pipelines - = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters, :user, :gcp]) do + = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do .nav-icon-container = sprite_icon('pipeline') %span.nav-item-name - CI / CD + = _('CI / CD') %ul.sidebar-sub-level-items - = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :environments, :artifacts, :clusters, :user, :gcp], html_options: { class: "fly-out-top-item" } ) do + = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], html_options: { class: "fly-out-top-item" } ) do = link_to project_pipelines_path(@project) do %strong.fly-out-top-item-name - #{ _('CI / CD') } + = _('CI / CD') %li.divider.fly-out-top-item - if project_nav_tab? :pipelines = nav_link(path: ['pipelines#index', 'pipelines#show']) do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do %span - Pipelines + = _('Pipelines') - if project_nav_tab? :builds = nav_link(controller: [:jobs, :artifacts]) do = link_to project_jobs_path(@project), title: 'Jobs', class: 'shortcuts-builds' do %span - Jobs + = _('Jobs') - if project_nav_tab? :pipelines = nav_link(controller: :pipeline_schedules) do = link_to pipeline_schedules_path(@project), title: 'Schedules', class: 'shortcuts-builds' do %span - Schedules + = _('Schedules') + + - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? + = nav_link(path: 'pipelines#charts') do + = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do + %span + = _('Charts') + + - if project_nav_tab? :operations + = nav_link(controller: [:environments, :clusters, :user, :gcp]) do + = link_to project_environments_path(@project), class: 'shortcuts-operations' do + .nav-icon-container + = sprite_icon('cloud-gear') + %span.nav-item-name + = _('Operations') + + %ul.sidebar-sub-level-items + = nav_link(controller: [:environments, :clusters, :user, :gcp], html_options: { class: "fly-out-top-item" } ) do + = link_to project_environments_path(@project) do + %strong.fly-out-top-item-name + = _('Operations') + %li.divider.fly-out-top-item - if project_nav_tab? :environments = nav_link(controller: :environments) do = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do %span - Environments + = _('Environments') - if project_nav_tab? :clusters - show_cluster_hint = show_gke_cluster_integration_callout?(@project) @@ -217,19 +238,18 @@ %span= _("Got it!") = sprite_icon('thumb-up') - - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? - = nav_link(path: 'pipelines#charts') do - = link_to charts_project_pipelines_path(@project), title: 'Charts', class: 'shortcuts-pipelines-charts' do - %span - Charts - - if project_nav_tab? :container_registry = nav_link(controller: %w[projects/registry/repositories]) do = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do .nav-icon-container = sprite_icon('disk') %span.nav-item-name - Registry + = _('Registry') + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(controller: %w[projects/registry/repositories], html_options: { class: "fly-out-top-item" } ) do + = link_to project_container_registry_index_path(@project) do + %strong.fly-out-top-item-name + = _('Registry') - if project_nav_tab? :wiki = nav_link(controller: :wikis) do @@ -237,12 +257,12 @@ .nav-icon-container = sprite_icon('book') %span.nav-item-name - Wiki + = _('Wiki') %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do = link_to get_project_wiki_path(@project) do %strong.fly-out-top-item-name - #{ _('Wiki') } + = _('Wiki') - if project_nav_tab? :snippets = nav_link(controller: :snippets) do @@ -250,12 +270,12 @@ .nav-icon-container = sprite_icon('snippet') %span.nav-item-name - Snippets + = _('Snippets') %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(controller: :snippets, html_options: { class: "fly-out-top-item" } ) do = link_to project_snippets_path(@project) do %strong.fly-out-top-item-name - #{ _('Snippets') } + = _('Snippets') - if project_nav_tab? :settings = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show]) do @@ -263,7 +283,7 @@ .nav-icon-container = sprite_icon('settings') %span.nav-item-name.qa-settings-item - Settings + = _('Settings') %ul.sidebar-sub-level-items - can_edit = can?(current_user, :admin_project, @project) @@ -271,16 +291,16 @@ = nav_link(path: %w[projects#edit project_members#index integrations#show services#edit repository#show ci_cd#show badges#index pages#show], html_options: { class: "fly-out-top-item" } ) do = link_to edit_project_path(@project) do %strong.fly-out-top-item-name - #{ _('Settings') } + = _('Settings') %li.divider.fly-out-top-item = nav_link(path: %w[projects#edit]) do = link_to edit_project_path(@project), title: 'General' do %span - General + = _('General') = nav_link(controller: :project_members) do = link_to project_project_members_path(@project), title: 'Members' do %span - Members + = _('Members') - if can_edit = nav_link(controller: :badges) do = link_to project_settings_badges_path(@project), title: _('Badges') do @@ -290,21 +310,21 @@ = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do = link_to project_settings_integrations_path(@project), title: 'Integrations' do %span - Integrations + = _('Integrations') = nav_link(controller: :repository) do = link_to project_settings_repository_path(@project), title: 'Repository' do %span - Repository + = _('Repository') - if @project.feature_available?(:builds, current_user) = nav_link(controller: :ci_cd) do = link_to project_settings_ci_cd_path(@project), title: 'CI / CD' do %span - CI / CD + = _('CI / CD') - if @project.pages_available? = nav_link(controller: :pages) do = link_to project_pages_path(@project), title: 'Pages' do %span - Pages + = _('Pages') - else = nav_link(controller: :project_members) do @@ -312,12 +332,12 @@ .nav-icon-container = sprite_icon('users') %span.nav-item-name - Members + = _('Members') %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(path: %w[members#show], html_options: { class: "fly-out-top-item" } ) do = link_to project_project_members_path(@project) do %strong.fly-out-top-item-name - #{ _('Members') } + = _('Members') = render 'shared/sidebar_toggle_button' diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 71e77dae69e..8cb6c446e18 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -35,7 +35,9 @@ = _('Domain') = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' .help-block - = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.') + = s_('CICD|A domain is required to use Auto Review Apps and Auto Deploy Stages.') + - if cluster_ingress_ip = cluster_ingress_ip(@project) + = s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe } = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank' = f.submit 'Save changes', class: "btn btn-success prepend-top-15" diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 9d3d4072027..35c7dc2984a 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -28,9 +28,16 @@ = link_to project_wiki_history_path(@project, @page), class: "btn" do = s_("Wiki|Page history") - if can?(current_user, :admin_wiki, @project) - = link_to project_wiki_path(@project, @page), data: { confirm: s_("WikiPageConfirmDelete|Are you sure you want to delete this page?")}, method: :delete, class: "btn btn-danger" do - = _("Delete") + %button.btn.btn-danger{ data: { toggle: 'modal', + target: '#delete-wiki-modal', + delete_wiki_url: project_wiki_path(@project, @page), + page_title: @page.title.capitalize }, + id: 'delete-wiki-button', + type: 'button' } + = _('Delete') = render 'form' = render 'sidebar' + +#delete-wiki-modal.modal.fade diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml index f5070c3867f..67476a3f573 100644 --- a/app/views/shared/boards/components/_board.html.haml +++ b/app/views/shared/boards/components/_board.html.haml @@ -15,7 +15,7 @@ ":title" => '(list.label ? list.label.description : "")', data: { container: "body", placement: "bottom" }, class: "badge color-label title board-title-text", - ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.text_color ? list.label.text_color : \"#2e2e2e\") }" } + ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" } {{ list.title }} - if can?(current_user, :admin_list, current_board_parent) diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 192d9ffb6f1..ddf54866b99 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -45,6 +45,6 @@ %strong.embed-toggle-list-item= _("Share") %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed } .input-group-btn - %button.js-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '#snippet-url-area' } + %button.js-clipboard-btn.snippet-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '.js-snippet-url-area' } = sprite_icon('duplicate', size: 16) .clearfix |