diff options
Diffstat (limited to 'app/assets/javascripts')
14 files changed, 192 insertions, 67 deletions
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 665a9c77822..489615f1f78 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -84,6 +84,9 @@ export default { ingressExternalIp() { return this.applications.ingress.externalIp; }, + certManagerInstalled() { + return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED; + }, ingressDescription() { const extraCostParagraph = sprintf( _.escape( @@ -130,9 +133,9 @@ export default { return sprintf( _.escape( s__( - `ClusterIntegration|cert-manager is a native Kubernetes certificate management controller that helps with issuing certificates. - Installing cert-manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates - are valid and up to date.`, + `ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. + Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates + are valid and up-to-date.`, ), ), { @@ -259,6 +262,16 @@ export default { </span> </div> <input v-else type="text" class="form-control js-ip-address" readonly value="?" /> + <p class="form-text text-muted"> + {{ + s__(`ClusterIntegration|Point a wildcard DNS to this + generated IP address in order to access + your application after it has been deployed.`) + }} + <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> + {{ __('More information') }} + </a> + </p> </div> <p v-if="!ingressExternalIp" class="settings-message js-no-ip-message"> @@ -272,17 +285,6 @@ export default { {{ __('More information') }} </a> </p> - - <p> - {{ - s__(`ClusterIntegration|Point a wildcard DNS to this - generated IP address in order to access - your application after it has been deployed.`) - }} - <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> - {{ __('More information') }} - </a> - </p> </template> <div v-html="ingressDescription"></div> </div> @@ -295,10 +297,41 @@ export default { :status-reason="applications.cert_manager.statusReason" :request-status="applications.cert_manager.requestStatus" :request-reason="applications.cert_manager.requestReason" + :install-application-request-params="{ email: applications.cert_manager.email }" :disabled="!helmInstalled" title-link="https://cert-manager.readthedocs.io/en/latest/#" > - <div slot="description" v-html="certManagerDescription"></div> + <template> + <div slot="description"> + <p v-html="certManagerDescription"></p> + <div class="form-group"> + <label for="cert-manager-issuer-email"> + {{ s__('ClusterIntegration|Issuer Email') }} + </label> + <div class="input-group"> + <input + v-model="applications.cert_manager.email" + :readonly="certManagerInstalled" + type="text" + class="form-control js-email" + /> + </div> + <p class="form-text text-muted"> + {{ + s__(`ClusterIntegration|Issuers represent a certificate authority. + You must provide an email address for your Issuer. `) + }} + <a + href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email" + target="_blank" + rel="noopener noreferrer" + > + {{ __('More information') }} + </a> + </p> + </div> + </div> + </template> </application-row> <application-row v-if="isProjectCluster" @@ -381,16 +414,17 @@ export default { /> </span> </div> + + <p v-if="ingressInstalled" class="form-text text-muted"> + {{ + s__(`ClusterIntegration|Replace this with your own hostname if you want. + If you do so, point hostname to Ingress IP Address from above.`) + }} + <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> + {{ __('More information') }} + </a> + </p> </div> - <p v-if="ingressInstalled"> - {{ - s__(`ClusterIntegration|Replace this with your own hostname if you want. - If you do so, point hostname to Ingress IP Address from above.`) - }} - <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> - {{ __('More information') }} - </a> - </p> </template> </div> </application-row> diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index 15cf4a56138..e31afadf186 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -24,3 +24,4 @@ export const REQUEST_FAILURE = 'request-failure'; export const INGRESS = 'ingress'; export const JUPYTER = 'jupyter'; export const KNATIVE = 'knative'; +export const CERT_MANAGER = 'cert_manager'; diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 2d69da8eaec..c750daab112 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -1,5 +1,5 @@ import { s__ } from '../../locale'; -import { INGRESS, JUPYTER, KNATIVE } from '../constants'; +import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER } from '../constants'; export default class ClusterStore { constructor() { @@ -30,6 +30,7 @@ export default class ClusterStore { statusReason: null, requestStatus: null, requestReason: null, + email: null, }, runner: { title: s__('ClusterIntegration|GitLab Runner'), @@ -103,6 +104,9 @@ export default class ClusterStore { if (appId === INGRESS) { this.state.applications.ingress.externalIp = serverAppEntry.external_ip; + } else if (appId === CERT_MANAGER) { + this.state.applications.cert_manager.email = + this.state.applications.cert_manager.email || serverAppEntry.email; } else if (appId === JUPYTER) { this.state.applications.jupyter.hostname = serverAppEntry.hostname || diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index c0456c18e44..952963e0711 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -192,8 +192,9 @@ export const toggleFileDiscussions = ({ getters, dispatch }, diff) => { }); }; -export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => { +export const saveDiffDiscussion = ({ state, dispatch }, { note, formData }) => { const postData = getNoteFormData({ + commit: state.commit, note, ...formData, }); diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 5c6b20109bb..cbaa0e26395 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -27,6 +27,7 @@ export const getReversePosition = linePosition => { export function getFormData(params) { const { + commit, note, noteableType, noteableData, @@ -66,7 +67,7 @@ export function getFormData(params) { position, noteable_type: noteableType, noteable_id: noteableData.id, - commit_id: '', + commit_id: commit && commit.id, type: diffFile.diff_refs.start_sha && diffFile.diff_refs.head_sha ? DIFF_NOTE_TYPE diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue index 4a9b2903eec..3cd3b743108 100644 --- a/app/assets/javascripts/jobs/components/trigger_block.vue +++ b/app/assets/javascripts/jobs/components/trigger_block.vue @@ -1,6 +1,9 @@ <script> +import { __ } from '~/locale'; import { GlButton } from '@gitlab/ui'; +const HIDDEN_VALUE = '••••••'; + export default { components: { GlButton, @@ -13,17 +16,26 @@ export default { }, data() { return { - areVariablesVisible: false, + showVariableValues: false, }; }, computed: { hasVariables() { return this.trigger.variables && this.trigger.variables.length > 0; }, + getToggleButtonText() { + return this.showVariableValues ? __('Hide values') : __('Reveal values'); + }, + hasValues() { + return this.trigger.variables.some(v => v.value); + }, }, methods: { - revealVariables() { - this.areVariablesVisible = true; + toggleValues() { + this.showVariableValues = !this.showVariableValues; + }, + getDisplayValue(value) { + return this.showVariableValues ? value : HIDDEN_VALUE; }, }, }; @@ -33,31 +45,33 @@ export default { <div class="build-widget block"> <h4 class="title">{{ __('Trigger') }}</h4> - <p v-if="trigger.short_token" class="js-short-token"> + <p + v-if="trigger.short_token" + class="js-short-token" + :class="{ 'append-bottom-0': !hasVariables }" + > <span class="build-light-text"> {{ __('Token') }} </span> {{ trigger.short_token }} </p> - <p v-if="hasVariables"> - <gl-button - v-if="!areVariablesVisible" - type="button" - class="btn btn-default group js-reveal-variables" - @click="revealVariables" - > - {{ __('Reveal Variables') }} - </gl-button> - </p> + <template v-if="hasVariables"> + <p class="trigger-variables-btn-container"> + <span class="build-light-text"> {{ __('Variables:') }} </span> - <dl v-if="areVariablesVisible" class="js-build-variables trigger-build-variables"> - <template v-for="variable in trigger.variables"> - <dt :key="`${variable.key}-variable`" class="js-build-variable trigger-build-variable"> - {{ variable.key }} - </dt> + <gl-button v-if="hasValues" class="group js-reveal-variables" @click="toggleValues"> + {{ getToggleButtonText }} + </gl-button> + </p> - <dd :key="`${variable.key}-value`" class="js-build-value trigger-build-value"> - {{ variable.value }} - </dd> - </template> - </dl> + <table class="js-build-variables trigger-build-variables"> + <tr v-for="(variable, index) in trigger.variables" :key="`${variable.key}-${index}`"> + <td class="js-build-variable trigger-build-variable trigger-variables-table-cell"> + {{ variable.key }} + </td> + <td class="js-build-value trigger-build-value trigger-variables-table-cell"> + {{ getDisplayValue(variable.value) }} + </td> + </tr> + </table> + </template> </div> </template> diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js index 6f42382246d..7933c234384 100644 --- a/app/assets/javascripts/lib/utils/dom_utils.js +++ b/app/assets/javascripts/lib/utils/dom_utils.js @@ -7,3 +7,8 @@ export const addClassIfElementExists = (element, className) => { }; export const isInVueNoteablePage = () => isInIssuePage() || isInEpicPage() || isInMRPage(); + +export const canScrollUp = ({ scrollTop }, margin = 0) => scrollTop > margin; + +export const canScrollDown = ({ scrollTop, offsetHeight, scrollHeight }, margin = 0) => + scrollTop + offsetHeight < scrollHeight - margin; diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index d32f39881dd..75c18a9b6a0 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -155,7 +155,7 @@ export default class MilestoneSelect { const { $el, e } = clickEvent; let selected = clickEvent.selectedObj; - let data, boardsStore; + let data, modalStoreFilter; if (!selected) return; if (options.handleClick) { @@ -179,11 +179,11 @@ export default class MilestoneSelect { } if ($dropdown.closest('.add-issues-modal').length) { - boardsStore = ModalStore.store.filter; + modalStoreFilter = ModalStore.store.filter; } - if (boardsStore) { - boardsStore[$dropdown.data('fieldName')] = selected.name; + if (modalStoreFilter) { + modalStoreFilter[$dropdown.data('fieldName')] = selected.name; e.preventDefault(); } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { return Issuable.filterResults($dropdown.closest('form')); diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 841fcec96e8..ce56beb1e6b 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -247,15 +247,19 @@ Please check your network connection and try again.`; } else { this.reopenIssue() .then(() => this.enableButton()) - .catch(() => { + .catch(({ data }) => { this.enableButton(); this.toggleStateButtonLoading(false); - Flash( - sprintf( - __('Something went wrong while reopening the %{issuable}. Please try again later'), - { issuable: this.noteableDisplayName }, - ), + let errorMessage = sprintf( + __('Something went wrong while reopening the %{issuable}. Please try again later'), + { issuable: this.noteableDisplayName }, ); + + if (data) { + errorMessage = Object.values(data).join('\n'); + } + + Flash(errorMessage); }); } }, diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index c4c8cf86cb0..e7fa05faa8a 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -12,6 +12,10 @@ export default function notificationsDropdown() { const form = $(this).parents('.notification-form:first'); form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner'); + if (form.hasClass('no-label')) { + form.find('.js-notification-loading').toggleClass('hidden'); + form.find('.js-notifications-icon').toggleClass('hidden'); + } form.find('#notification_setting_level').val(notificationLevel); form.submit(); }); diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index a6bee49a6b1..b288989b252 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -13,6 +13,9 @@ export default class Project { const $cloneOptions = $('ul.clone-options-dropdown'); const $projectCloneField = $('#project_clone'); const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label'); + const mobileCloneField = document.querySelector( + '.js-mobile-git-clone .js-clone-dropdown-label', + ); const selectedCloneOption = $cloneBtnLabel.text().trim(); if (selectedCloneOption.length > 0) { @@ -36,7 +39,11 @@ export default class Project { $label.text(activeText); }); - $projectCloneField.val(url); + if (mobileCloneField) { + mobileCloneField.dataset.clipboardText = url; + } else { + $projectCloneField.val(url); + } $('.js-git-empty .js-clone').text(url); }); // Ref switcher diff --git a/app/assets/javascripts/terminal/index.js b/app/assets/javascripts/terminal/index.js index 49aeb377c74..8faff59fd45 100644 --- a/app/assets/javascripts/terminal/index.js +++ b/app/assets/javascripts/terminal/index.js @@ -1,3 +1,3 @@ import Terminal from './terminal'; -export default () => new Terminal({ selector: '#terminal' }); +export default () => new Terminal(document.getElementById('terminal')); diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js index b24aa8a3a34..560f50ebf8f 100644 --- a/app/assets/javascripts/terminal/terminal.js +++ b/app/assets/javascripts/terminal/terminal.js @@ -1,9 +1,15 @@ +import _ from 'underscore'; import $ from 'jquery'; import { Terminal } from 'xterm'; import * as fit from 'xterm/lib/addons/fit/fit'; +import { canScrollUp, canScrollDown } from '~/lib/utils/dom_utils'; + +const SCROLL_MARGIN = 5; + +Terminal.applyAddon(fit); export default class GLTerminal { - constructor(options = {}) { + constructor(element, options = {}) { this.options = Object.assign( {}, { @@ -13,7 +19,8 @@ export default class GLTerminal { options, ); - this.container = document.querySelector(options.selector); + this.container = element; + this.onDispose = []; this.setSocketUrl(); this.createTerminal(); @@ -34,8 +41,6 @@ export default class GLTerminal { } createTerminal() { - Terminal.applyAddon(fit); - this.terminal = new Terminal(this.options); this.socket = new WebSocket(this.socketUrl, ['terminal.gitlab.com']); @@ -72,4 +77,48 @@ export default class GLTerminal { handleSocketFailure() { this.terminal.write('\r\nConnection failure'); } + + addScrollListener(onScrollLimit) { + const viewport = this.container.querySelector('.xterm-viewport'); + const listener = _.throttle(() => { + onScrollLimit({ + canScrollUp: canScrollUp(viewport, SCROLL_MARGIN), + canScrollDown: canScrollDown(viewport, SCROLL_MARGIN), + }); + }); + + this.onDispose.push(() => viewport.removeEventListener('scroll', listener)); + viewport.addEventListener('scroll', listener); + + // don't forget to initialize value before scroll! + listener({ target: viewport }); + } + + disable() { + this.terminal.setOption('cursorBlink', false); + this.terminal.setOption('theme', { foreground: '#707070' }); + this.terminal.setOption('disableStdin', true); + this.socket.close(); + } + + dispose() { + this.terminal.off('data'); + this.terminal.dispose(); + this.socket.close(); + + this.onDispose.forEach(fn => fn()); + this.onDispose.length = 0; + } + + scrollToTop() { + this.terminal.scrollToTop(); + } + + scrollToBottom() { + this.terminal.scrollToBottom(); + } + + fit() { + this.terminal.fit(); + } } diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue index e742900dbcb..373794fb1f2 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue @@ -44,6 +44,7 @@ export default { class="sidebar-collapsed-icon" data-placement="left" data-container="body" + data-boundary="viewport" @click="handleClick" > <i aria-hidden="true" data-hidden="true" class="fa fa-tags"> </i> |