Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-04 15:09:00 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-04 15:09:00 +0300
commit88a0824944720b6edaaef56376713541b9a02118 (patch)
treef5fcc4f9755f249779cda9a8f02902d734af6e7e /app/assets
parent7d19df2d34a9803d9f077c16315ba919b7ae2aa2 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue4
-rw-r--r--app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue35
-rw-r--r--app/assets/javascripts/jobs/index.js7
-rw-r--r--app/assets/javascripts/jobs/store/actions.js39
-rw-r--r--app/assets/javascripts/jobs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js9
-rw-r--r--app/assets/javascripts/jobs/store/state.js1
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue61
-rw-r--r--app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue197
-rw-r--r--app/assets/javascripts/registry/shared/components/expiration_policy_form.vue247
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue8
12 files changed, 304 insertions, 308 deletions
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
index 3d389cf3db5..59c5586edcd 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue
@@ -306,9 +306,9 @@ export default {
</script>
<template>
<form name="eks-cluster-configuration-form">
- <h2>
+ <h4>
{{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }}
- </h2>
+ </h4>
<div class="mb-3" v-html="kubernetesIntegrationHelpText"></div>
<div class="form-group">
<label class="label-bold" for="eks-cluster-name">{{
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
index 49a5d4657af..0cfe47dafaf 100644
--- a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
+++ b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue
@@ -83,7 +83,7 @@ export default {
</script>
<template>
<form name="service-credentials-form">
- <h2>{{ s__('ClusterIntegration|Authenticate with Amazon Web Services') }}</h2>
+ <h4>{{ s__('ClusterIntegration|Authenticate with Amazon Web Services') }}</h4>
<p>
{{
s__(
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 809b3d5f57e..0ca13e897f3 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -8,7 +8,6 @@ import { polyfillSticky } from '~/lib/utils/sticky';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
import Icon from '~/vue_shared/components/icon.vue';
-import createStore from '../store';
import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
@@ -22,7 +21,6 @@ import { isNewJobLogActive } from '../store/utils';
export default {
name: 'JobPageApp',
- store: createStore(),
components: {
CiHeader,
Callout,
@@ -60,27 +58,15 @@ export default {
required: false,
default: null,
},
- endpoint: {
- type: String,
- required: true,
- },
terminalPath: {
type: String,
required: false,
default: null,
},
- pagePath: {
- type: String,
- required: true,
- },
projectPath: {
type: String,
required: true,
},
- logState: {
- type: String,
- required: true,
- },
subscriptionsMoreMinutesUrl: {
type: String,
required: false,
@@ -161,37 +147,28 @@ export default {
created() {
this.throttled = _.throttle(this.toggleScrollButtons, 100);
- this.setJobEndpoint(this.endpoint);
- this.setTraceOptions({
- logState: this.logState,
- pagePath: this.pagePath,
- });
-
- this.fetchJob();
- this.fetchTrace();
-
window.addEventListener('resize', this.onResize);
window.addEventListener('scroll', this.updateScroll);
},
mounted() {
this.updateSidebar();
},
- destroyed() {
+ beforeDestroy() {
+ this.stopPollingTrace();
+ this.stopPolling();
window.removeEventListener('resize', this.onResize);
window.removeEventListener('scroll', this.updateScroll);
},
methods: {
...mapActions([
- 'setJobEndpoint',
- 'setTraceOptions',
- 'fetchJob',
'fetchJobsForStage',
'hideSidebar',
'showSidebar',
'toggleSidebar',
- 'fetchTrace',
'scrollBottom',
'scrollTop',
+ 'stopPollingTrace',
+ 'stopPolling',
'toggleScrollButtons',
'toggleScrollAnimation',
]),
@@ -223,7 +200,7 @@ export default {
<div>
<gl-loading-icon
v-if="isLoading"
- :size="2"
+ size="lg"
class="js-job-loading qa-loading-animation prepend-top-20"
/>
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index 9c35534523e..024a13ce102 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -1,11 +1,18 @@
import Vue from 'vue';
import JobApp from './components/job_app.vue';
+import createStore from './store';
export default () => {
const element = document.getElementById('js-job-vue-app');
+ const store = createStore();
+
+ // Let's start initializing the store (i.e. fetching data) right away
+ store.dispatch('init', element.dataset);
+
return new Vue({
el: element,
+ store,
components: {
JobApp,
},
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 41cc5a181dc..f4030939f2c 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -14,6 +14,16 @@ import {
scrollUp,
} from '~/lib/utils/scroll_utils';
+export const init = ({ dispatch }, { endpoint, logState, pagePath }) => {
+ dispatch('setJobEndpoint', endpoint);
+ dispatch('setTraceOptions', {
+ logState,
+ pagePath,
+ });
+
+ return Promise.all([dispatch('fetchJob'), dispatch('fetchTrace')]);
+};
+
export const setJobEndpoint = ({ commit }, endpoint) => commit(types.SET_JOB_ENDPOINT, endpoint);
export const setTraceOptions = ({ commit }, options) => commit(types.SET_TRACE_OPTIONS, options);
@@ -147,7 +157,6 @@ export const toggleScrollisInBottom = ({ commit }, toggle) => {
export const requestTrace = ({ commit }) => commit(types.REQUEST_TRACE);
-let traceTimeout;
export const fetchTrace = ({ dispatch, state }) =>
axios
.get(`${state.traceEndpoint}/trace.json`, {
@@ -157,24 +166,32 @@ export const fetchTrace = ({ dispatch, state }) =>
dispatch('toggleScrollisInBottom', isScrolledToBottom());
dispatch('receiveTraceSuccess', data);
- if (!data.complete) {
- traceTimeout = setTimeout(() => {
- dispatch('fetchTrace');
- }, 4000);
- } else {
+ if (data.complete) {
dispatch('stopPollingTrace');
+ } else if (!state.traceTimeout) {
+ dispatch('startPollingTrace');
}
})
.catch(() => dispatch('receiveTraceError'));
-export const stopPollingTrace = ({ commit }) => {
+export const startPollingTrace = ({ dispatch, commit }) => {
+ const traceTimeout = setTimeout(() => {
+ commit(types.SET_TRACE_TIMEOUT, 0);
+ dispatch('fetchTrace');
+ }, 4000);
+
+ commit(types.SET_TRACE_TIMEOUT, traceTimeout);
+};
+
+export const stopPollingTrace = ({ state, commit }) => {
+ clearTimeout(state.traceTimeout);
+ commit(types.SET_TRACE_TIMEOUT, 0);
commit(types.STOP_POLLING_TRACE);
- clearTimeout(traceTimeout);
};
+
export const receiveTraceSuccess = ({ commit }, log) => commit(types.RECEIVE_TRACE_SUCCESS, log);
-export const receiveTraceError = ({ commit }) => {
- commit(types.RECEIVE_TRACE_ERROR);
- clearTimeout(traceTimeout);
+export const receiveTraceError = ({ dispatch }) => {
+ dispatch('stopPollingTrace');
flash(__('An error occurred while fetching the job log.'));
};
/**
diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js
index 858fa3b73ab..6c4f1b5a191 100644
--- a/app/assets/javascripts/jobs/store/mutation_types.js
+++ b/app/assets/javascripts/jobs/store/mutation_types.js
@@ -10,7 +10,6 @@ export const DISABLE_SCROLL_BOTTOM = 'DISABLE_SCROLL_BOTTOM';
export const DISABLE_SCROLL_TOP = 'DISABLE_SCROLL_TOP';
export const ENABLE_SCROLL_BOTTOM = 'ENABLE_SCROLL_BOTTOM';
export const ENABLE_SCROLL_TOP = 'ENABLE_SCROLL_TOP';
-// TODO
export const TOGGLE_SCROLL_ANIMATION = 'TOGGLE_SCROLL_ANIMATION';
export const TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE = 'TOGGLE_IS_SCROLL_IN_BOTTOM';
@@ -20,6 +19,7 @@ export const RECEIVE_JOB_SUCCESS = 'RECEIVE_JOB_SUCCESS';
export const RECEIVE_JOB_ERROR = 'RECEIVE_JOB_ERROR';
export const REQUEST_TRACE = 'REQUEST_TRACE';
+export const SET_TRACE_TIMEOUT = 'SET_TRACE_TIMEOUT';
export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE';
export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS';
export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR';
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index 77c68cac4a6..6193d8d34ab 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -53,17 +53,14 @@ export default {
state.isTraceComplete = log.complete || state.isTraceComplete;
},
- /**
- * Will remove loading animation
- */
- [types.STOP_POLLING_TRACE](state) {
- state.isTraceComplete = true;
+ [types.SET_TRACE_TIMEOUT](state, id) {
+ state.traceTimeout = id;
},
/**
* Will remove loading animation
*/
- [types.RECEIVE_TRACE_ERROR](state) {
+ [types.STOP_POLLING_TRACE](state) {
state.isTraceComplete = true;
},
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index cdc1780f3d6..5a61828ec6d 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -22,6 +22,7 @@ export default () => ({
isTraceComplete: false,
traceSize: 0,
isTraceSizeVisible: false,
+ traceTimeout: 0,
// used as a query parameter to fetch the trace
traceState: null,
diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue
index ad2fdb4fd40..cab3c7fff85 100644
--- a/app/assets/javascripts/registry/settings/components/settings_form.vue
+++ b/app/assets/javascripts/registry/settings/components/settings_form.vue
@@ -1,16 +1,20 @@
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
+import { GlCard, GlButton, GlLoadingIcon } from '@gitlab/ui';
import Tracking from '~/tracking';
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '../../shared/constants';
import { mapComputed } from '~/vuex_shared/bindings';
-import ExpirationPolicyForm from '../../shared/components/expiration_policy_form.vue';
+import ExpirationPolicyFields from '../../shared/components/expiration_policy_fields.vue';
export default {
components: {
- ExpirationPolicyForm,
+ GlCard,
+ GlButton,
+ GlLoadingIcon,
+ ExpirationPolicyFields,
},
mixins: [Tracking.mixin()],
labelsConfig: {
@@ -22,12 +26,19 @@ export default {
tracking: {
label: 'docker_container_retention_and_expiration_policies',
},
+ formIsValid: true,
};
},
computed: {
...mapState(['formOptions', 'isLoading']),
...mapGetters({ isEdited: 'getIsEdited' }),
...mapComputed([{ key: 'settings', getter: 'getSettings' }], 'updateSettings'),
+ isSubmitButtonDisabled() {
+ return !this.formIsValid || this.isLoading;
+ },
+ isCancelButtonDisabled() {
+ return !this.isEdited || this.isLoading;
+ },
},
methods: {
...mapActions(['resetSettings', 'saveSettings']),
@@ -46,12 +57,42 @@ export default {
</script>
<template>
- <expiration-policy-form
- v-model="settings"
- :form-options="formOptions"
- :is-loading="isLoading"
- :disable-cancel-button="!isEdited"
- @submit="submit"
- @reset="reset"
- />
+ <form ref="form-element" @submit.prevent="submit" @reset.prevent="reset">
+ <gl-card>
+ <template #header>
+ {{ s__('ContainerRegistry|Tag expiration policy') }}
+ </template>
+ <template #default>
+ <expiration-policy-fields
+ v-model="settings"
+ :form-options="formOptions"
+ :is-loading="isLoading"
+ @validated="formIsValid = true"
+ @invalidated="formIsValid = false"
+ />
+ </template>
+ <template #footer>
+ <div class="d-flex justify-content-end">
+ <gl-button
+ ref="cancel-button"
+ type="reset"
+ class="mr-2 d-block"
+ :disabled="isCancelButtonDisabled"
+ >
+ {{ __('Cancel') }}
+ </gl-button>
+ <gl-button
+ ref="save-button"
+ type="submit"
+ :disabled="isSubmitButtonDisabled"
+ variant="success"
+ class="d-flex justify-content-center align-items-center js-no-auto-disable"
+ >
+ {{ __('Save expiration policy') }}
+ <gl-loading-icon v-if="isLoading" class="ml-2" />
+ </gl-button>
+ </div>
+ </template>
+ </gl-card>
+ </form>
</template>
diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue
new file mode 100644
index 00000000000..84d1c5ccc6a
--- /dev/null
+++ b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue
@@ -0,0 +1,197 @@
+<script>
+import { uniqueId } from 'lodash';
+import { GlFormGroup, GlToggle, GlFormSelect, GlFormTextarea } from '@gitlab/ui';
+import { s__, __, sprintf } from '~/locale';
+import { NAME_REGEX_LENGTH } from '../constants';
+import { mapComputedToEvent } from '../utils';
+
+export default {
+ components: {
+ GlFormGroup,
+ GlToggle,
+ GlFormSelect,
+ GlFormTextarea,
+ },
+ props: {
+ formOptions: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ value: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ labelCols: {
+ type: [Number, String],
+ required: false,
+ default: 3,
+ },
+ labelAlign: {
+ type: String,
+ required: false,
+ default: 'right',
+ },
+ },
+ nameRegexPlaceholder: '.*',
+ selectList: [
+ {
+ name: 'expiration-policy-interval',
+ label: s__('ContainerRegistry|Expiration interval:'),
+ model: 'older_than',
+ optionKey: 'olderThan',
+ },
+ {
+ name: 'expiration-policy-schedule',
+ label: s__('ContainerRegistry|Expiration schedule:'),
+ model: 'cadence',
+ optionKey: 'cadence',
+ },
+ {
+ name: 'expiration-policy-latest',
+ label: s__('ContainerRegistry|Number of tags to retain:'),
+ model: 'keep_n',
+ optionKey: 'keepN',
+ },
+ ],
+ data() {
+ return {
+ uniqueId: uniqueId(),
+ };
+ },
+ computed: {
+ ...mapComputedToEvent(['enabled', 'cadence', 'older_than', 'keep_n', 'name_regex'], 'value'),
+ policyEnabledText() {
+ return this.enabled ? __('enabled') : __('disabled');
+ },
+ toggleDescriptionText() {
+ return sprintf(
+ s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'),
+ {
+ toggleStatus: `<strong>${this.policyEnabledText}</strong>`,
+ },
+ false,
+ );
+ },
+ regexHelpText() {
+ return sprintf(
+ s__(
+ 'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
+ ),
+ {
+ codeStart: '<code>',
+ codeEnd: '</code>',
+ },
+ false,
+ );
+ },
+ nameRegexState() {
+ return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null;
+ },
+ fieldsValidity() {
+ return this.nameRegexState !== false;
+ },
+ isFormElementDisabled() {
+ return !this.enabled || this.isLoading;
+ },
+ },
+ watch: {
+ fieldsValidity: {
+ immediate: true,
+ handler(valid) {
+ if (valid) {
+ this.$emit('validated');
+ } else {
+ this.$emit('invalidated');
+ }
+ },
+ },
+ },
+ methods: {
+ idGenerator(id) {
+ return `${id}_${this.uniqueId}`;
+ },
+ updateModel(value, key) {
+ this[key] = value;
+ },
+ },
+};
+</script>
+
+<template>
+ <div ref="form-elements" class="lh-2">
+ <gl-form-group
+ :id="idGenerator('expiration-policy-toggle-group')"
+ :label-cols="labelCols"
+ :label-align="labelAlign"
+ :label-for="idGenerator('expiration-policy-toggle')"
+ :label="s__('ContainerRegistry|Expiration policy:')"
+ >
+ <div class="d-flex align-items-start">
+ <gl-toggle
+ :id="idGenerator('expiration-policy-toggle')"
+ v-model="enabled"
+ :disabled="isLoading"
+ />
+ <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span>
+ </div>
+ </gl-form-group>
+
+ <gl-form-group
+ v-for="select in $options.selectList"
+ :id="idGenerator(`${select.name}-group`)"
+ :key="select.name"
+ :label-cols="labelCols"
+ :label-align="labelAlign"
+ :label-for="idGenerator(select.name)"
+ :label="select.label"
+ >
+ <gl-form-select
+ :id="idGenerator(select.name)"
+ :value="value[select.model]"
+ :disabled="isFormElementDisabled"
+ @input="updateModel($event, select.model)"
+ >
+ <option
+ v-for="option in formOptions[select.optionKey]"
+ :key="option.key"
+ :value="option.key"
+ >
+ {{ option.label }}
+ </option>
+ </gl-form-select>
+ </gl-form-group>
+
+ <gl-form-group
+ :id="idGenerator('expiration-policy-name-matching-group')"
+ :label-cols="labelCols"
+ :label-align="labelAlign"
+ :label-for="idGenerator('expiration-policy-name-matching')"
+ :label="
+ s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:')
+ "
+ :state="nameRegexState"
+ :invalid-feedback="
+ s__('ContainerRegistry|The value of this input should be less than 255 characters')
+ "
+ >
+ <gl-form-textarea
+ :id="idGenerator('expiration-policy-name-matching')"
+ v-model="name_regex"
+ :placeholder="$options.nameRegexPlaceholder"
+ :state="nameRegexState"
+ :disabled="isFormElementDisabled"
+ trim
+ />
+ <template #description>
+ <span ref="regex-description" v-html="regexHelpText"></span>
+ </template>
+ </gl-form-group>
+ </div>
+</template>
diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue
deleted file mode 100644
index c044add3759..00000000000
--- a/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue
+++ /dev/null
@@ -1,247 +0,0 @@
-<script>
-import { uniqueId } from 'lodash';
-import {
- GlFormGroup,
- GlToggle,
- GlFormSelect,
- GlFormTextarea,
- GlButton,
- GlCard,
- GlLoadingIcon,
-} from '@gitlab/ui';
-import { s__, __, sprintf } from '~/locale';
-import { NAME_REGEX_LENGTH } from '../constants';
-import { mapComputedToEvent } from '../utils';
-
-export default {
- components: {
- GlFormGroup,
- GlToggle,
- GlFormSelect,
- GlFormTextarea,
- GlButton,
- GlCard,
- GlLoadingIcon,
- },
- props: {
- formOptions: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
- value: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- labelCols: {
- type: [Number, String],
- required: false,
- default: 3,
- },
- labelAlign: {
- type: String,
- required: false,
- default: 'right',
- },
- disableCancelButton: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
- nameRegexPlaceholder: '.*',
- data() {
- return {
- uniqueId: uniqueId(),
- };
- },
- computed: {
- ...mapComputedToEvent(['enabled', 'cadence', 'older_than', 'keep_n', 'name_regex'], 'value'),
- policyEnabledText() {
- return this.enabled ? __('enabled') : __('disabled');
- },
- toggleDescriptionText() {
- return sprintf(
- s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'),
- {
- toggleStatus: `<strong>${this.policyEnabledText}</strong>`,
- },
- false,
- );
- },
- regexHelpText() {
- return sprintf(
- s__(
- 'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
- ),
- {
- codeStart: '<code>',
- codeEnd: '</code>',
- },
- false,
- );
- },
- nameRegexState() {
- return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null;
- },
- formIsInvalid() {
- return this.nameRegexState === false;
- },
- isFormElementDisabled() {
- return !this.enabled || this.isLoading;
- },
- isSubmitButtonDisabled() {
- return this.formIsInvalid || this.isLoading;
- },
- isCancelButtonDisabled() {
- return this.disableCancelButton || this.isLoading;
- },
- },
- methods: {
- idGenerator(id) {
- return `${id}_${this.uniqueId}`;
- },
- },
-};
-</script>
-
-<template>
- <form
- ref="form-element"
- class="lh-2"
- @submit.prevent="$emit('submit')"
- @reset.prevent="$emit('reset')"
- >
- <gl-card>
- <template #header>
- {{ s__('ContainerRegistry|Tag expiration policy') }}
- </template>
- <template>
- <gl-form-group
- :id="idGenerator('expiration-policy-toggle-group')"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator('expiration-policy-toggle')"
- :label="s__('ContainerRegistry|Expiration policy:')"
- >
- <div class="d-flex align-items-start">
- <gl-toggle
- :id="idGenerator('expiration-policy-toggle')"
- v-model="enabled"
- :disabled="isLoading"
- />
- <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span>
- </div>
- </gl-form-group>
-
- <gl-form-group
- :id="idGenerator('expiration-policy-interval-group')"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator('expiration-policy-interval')"
- :label="s__('ContainerRegistry|Expiration interval:')"
- >
- <gl-form-select
- :id="idGenerator('expiration-policy-interval')"
- v-model="older_than"
- :disabled="isFormElementDisabled"
- >
- <option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key">
- {{ option.label }}
- </option>
- </gl-form-select>
- </gl-form-group>
-
- <gl-form-group
- :id="idGenerator('expiration-policy-schedule-group')"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator('expiration-policy-schedule')"
- :label="s__('ContainerRegistry|Expiration schedule:')"
- >
- <gl-form-select
- :id="idGenerator('expiration-policy-schedule')"
- v-model="cadence"
- :disabled="isFormElementDisabled"
- >
- <option v-for="option in formOptions.cadence" :key="option.key" :value="option.key">
- {{ option.label }}
- </option>
- </gl-form-select>
- </gl-form-group>
-
- <gl-form-group
- :id="idGenerator('expiration-policy-latest-group')"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator('expiration-policy-latest')"
- :label="s__('ContainerRegistry|Number of tags to retain:')"
- >
- <gl-form-select
- :id="idGenerator('expiration-policy-latest')"
- v-model="keep_n"
- :disabled="isFormElementDisabled"
- >
- <option v-for="option in formOptions.keepN" :key="option.key" :value="option.key">
- {{ option.label }}
- </option>
- </gl-form-select>
- </gl-form-group>
-
- <gl-form-group
- :id="idGenerator('expiration-policy-name-matching-group')"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator('expiration-policy-name-matching')"
- :label="
- s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:')
- "
- :state="nameRegexState"
- :invalid-feedback="
- s__('ContainerRegistry|The value of this input should be less than 255 characters')
- "
- >
- <gl-form-textarea
- :id="idGenerator('expiration-policy-name-matching')"
- v-model="name_regex"
- :placeholder="$options.nameRegexPlaceholder"
- :state="nameRegexState"
- :disabled="isFormElementDisabled"
- trim
- />
- <template #description>
- <span ref="regex-description" v-html="regexHelpText"></span>
- </template>
- </gl-form-group>
- </template>
- <template #footer>
- <div class="d-flex justify-content-end">
- <gl-button
- ref="cancel-button"
- type="reset"
- class="mr-2 d-block"
- :disabled="isCancelButtonDisabled"
- >
- {{ __('Cancel') }}
- </gl-button>
- <gl-button
- ref="save-button"
- type="submit"
- :disabled="isSubmitButtonDisabled"
- variant="success"
- class="d-flex justify-content-center align-items-center js-no-auto-disable"
- >
- {{ __('Save expiration policy') }}
- <gl-loading-icon v-if="isLoading" class="ml-2" />
- </gl-button>
- </div>
- </template>
- </gl-card>
- </form>
-</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 4f5f3ee5cf9..e30876813c2 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -79,6 +79,12 @@ export default {
required: false,
default: false,
},
+ // This prop is used as a fallback in case if textarea.elm is undefined
+ textareaValue: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -183,7 +189,7 @@ export default {
Can't use `$refs` as the component is technically in the parent component
so we access the VNode & then get the element
*/
- const text = this.$slots.textarea[0].elm.value;
+ const text = this.$slots.textarea[0]?.elm?.value || this.textareaValue;
if (text) {
this.markdownPreviewLoading = true;