summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/ci_variable_list/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/ci_variable_list/components')
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_key_field.vue169
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js14
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue81
3 files changed, 41 insertions, 223 deletions
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue b/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue
deleted file mode 100644
index c15d638d92b..00000000000
--- a/app/assets/javascripts/ci_variable_list/components/ci_key_field.vue
+++ /dev/null
@@ -1,169 +0,0 @@
-<script>
-import { uniqueId } from 'lodash';
-import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
-
-export default {
- name: 'CiKeyField',
- components: {
- GlButton,
- GlFormGroup,
- GlFormInput,
- },
- model: {
- prop: 'value',
- event: 'input',
- },
- props: {
- tokenList: {
- type: Array,
- required: true,
- },
- value: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- results: [],
- arrowCounter: -1,
- userDismissedResults: false,
- suggestionsId: uniqueId('token-suggestions-'),
- };
- },
- computed: {
- showAutocomplete() {
- return this.showSuggestions ? 'off' : 'on';
- },
- showSuggestions() {
- return this.results.length > 0;
- },
- },
- mounted() {
- document.addEventListener('click', this.handleClickOutside);
- },
- destroyed() {
- document.removeEventListener('click', this.handleClickOutside);
- },
- methods: {
- closeSuggestions() {
- this.results = [];
- this.arrowCounter = -1;
- },
- handleClickOutside(event) {
- if (!this.$el.contains(event.target)) {
- this.closeSuggestions();
- }
- },
- onArrowDown() {
- const newCount = this.arrowCounter + 1;
-
- if (newCount >= this.results.length) {
- this.arrowCounter = 0;
- return;
- }
-
- this.arrowCounter = newCount;
- },
- onArrowUp() {
- const newCount = this.arrowCounter - 1;
-
- if (newCount < 0) {
- this.arrowCounter = this.results.length - 1;
- return;
- }
-
- this.arrowCounter = newCount;
- },
- onEnter() {
- const currentToken = this.results[this.arrowCounter] || this.value;
- this.selectToken(currentToken);
- },
- onEsc() {
- if (!this.showSuggestions) {
- this.$emit('input', '');
- }
- this.closeSuggestions();
- this.userDismissedResults = true;
- },
- onEntry(value) {
- this.$emit('input', value);
- this.userDismissedResults = false;
-
- // short circuit so that we don't false match on empty string
- if (value.length < 1) {
- this.closeSuggestions();
- return;
- }
-
- const filteredTokens = this.tokenList.filter(token =>
- token.toLowerCase().includes(value.toLowerCase()),
- );
-
- if (filteredTokens.length) {
- this.openSuggestions(filteredTokens);
- } else {
- this.closeSuggestions();
- }
- },
- openSuggestions(filteredResults) {
- this.results = filteredResults;
- },
- selectToken(value) {
- this.$emit('input', value);
- this.closeSuggestions();
- this.$emit('key-selected');
- },
- },
-};
-</script>
-<template>
- <div>
- <div class="dropdown position-relative" role="combobox" aria-owns="token-suggestions">
- <gl-form-group :label="__('Key')" label-for="ci-variable-key">
- <gl-form-input
- id="ci-variable-key"
- :value="value"
- type="text"
- role="searchbox"
- class="form-control pl-2 js-env-input"
- :autocomplete="showAutocomplete"
- aria-autocomplete="list"
- aria-controls="token-suggestions"
- aria-haspopup="listbox"
- :aria-expanded="showSuggestions"
- data-qa-selector="ci_variable_key_field"
- @input="onEntry"
- @keydown.down="onArrowDown"
- @keydown.up="onArrowUp"
- @keydown.enter.prevent="onEnter"
- @keydown.esc.stop="onEsc"
- @keydown.tab="closeSuggestions"
- />
- </gl-form-group>
-
- <div
- v-show="showSuggestions && !userDismissedResults"
- id="ci-variable-dropdown"
- class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width"
- :class="{ 'd-block': showSuggestions }"
- >
- <div class="dropdown-content">
- <ul :id="suggestionsId">
- <li
- v-for="(result, i) in results"
- :key="i"
- role="option"
- :class="{ 'gl-bg-gray-50': i === arrowCounter }"
- :aria-selected="i === arrowCounter"
- >
- <gl-button tabindex="-1" class="btn-transparent pl-2" @click="selectToken(result)">{{
- result
- }}</gl-button>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js b/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js
index 9022bf51514..3f25e3df305 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_autocomplete_tokens.js
@@ -1,28 +1,14 @@
-import { __ } from '~/locale';
-
import { AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION, AWS_SECRET_ACCESS_KEY } from '../constants';
export const awsTokens = {
[AWS_ACCESS_KEY_ID]: {
name: AWS_ACCESS_KEY_ID,
- /* Checks for exactly twenty characters that match key.
- Based on greps suggested by Amazon at:
- https://aws.amazon.com/blogs/security/a-safer-way-to-distribute-aws-credentials-to-ec2/
- */
- validation: val => /^[A-Za-z0-9]{20}$/.test(val),
- invalidMessage: __('This variable does not match the expected pattern.'),
},
[AWS_DEFAULT_REGION]: {
name: AWS_DEFAULT_REGION,
},
[AWS_SECRET_ACCESS_KEY]: {
name: AWS_SECRET_ACCESS_KEY,
- /* Checks for exactly forty characters that match secret.
- Based on greps suggested by Amazon at:
- https://aws.amazon.com/blogs/security/a-safer-way-to-distribute-aws-credentials-to-ec2/
- */
- validation: val => /^[A-Za-z0-9/+=]{40}$/.test(val),
- invalidMessage: __('This variable does not match the expected pattern.'),
},
};
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
index 6531b945212..0ba58430de1 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
@@ -5,6 +5,7 @@ import {
GlCollapse,
GlDeprecatedButton,
GlFormCheckbox,
+ GlFormCombobox,
GlFormGroup,
GlFormInput,
GlFormSelect,
@@ -16,6 +17,7 @@ import {
} from '@gitlab/ui';
import Cookies from 'js-cookie';
import { mapActions, mapState } from 'vuex';
+import { mapComputed } from '~/vuex_shared/bindings';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
@@ -25,19 +27,21 @@ import {
AWS_TIP_MESSAGE,
} from '../constants';
import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens';
-import CiKeyField from './ci_key_field.vue';
import CiEnvironmentsDropdown from './ci_environments_dropdown.vue';
export default {
modalId: ADD_CI_VARIABLE_MODAL_ID,
+ tokens: awsTokens,
+ tokenList: awsTokenList,
+ awsTipMessage: AWS_TIP_MESSAGE,
components: {
CiEnvironmentsDropdown,
- CiKeyField,
GlAlert,
GlButton,
GlCollapse,
GlDeprecatedButton,
GlFormCheckbox,
+ GlFormCombobox,
GlFormGroup,
GlFormInput,
GlFormSelect,
@@ -48,9 +52,6 @@ export default {
GlSprintf,
},
mixins: [glFeatureFlagsMixin()],
- tokens: awsTokens,
- tokenList: awsTokenList,
- awsTipMessage: AWS_TIP_MESSAGE,
data() {
return {
isTipDismissed: Cookies.get(AWS_TIP_DISMISSED_COOKIE_NAME) === 'true',
@@ -74,22 +75,34 @@ export default {
'protectedEnvironmentVariablesLink',
'maskedEnvironmentVariablesLink',
]),
+ ...mapComputed(
+ [
+ { key: 'key', updateFn: 'updateVariableKey' },
+ { key: 'secret_value', updateFn: 'updateVariableValue' },
+ { key: 'variable_type', updateFn: 'updateVariableType' },
+ { key: 'environment_scope', updateFn: 'setEnvironmentScope' },
+ { key: 'protected_variable', updateFn: 'updateVariableProtected' },
+ { key: 'masked', updateFn: 'updateVariableMasked' },
+ ],
+ false,
+ 'variable',
+ ),
isTipVisible() {
- return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variableData.key);
+ return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key);
},
canSubmit() {
return (
this.variableValidationState &&
- this.variableData.key !== '' &&
- this.variableData.secret_value !== ''
+ this.variable.key !== '' &&
+ this.variable.secret_value !== ''
);
},
canMask() {
const regex = RegExp(this.maskableRegex);
- return regex.test(this.variableData.secret_value);
+ return regex.test(this.variable.secret_value);
},
displayMaskedError() {
- return !this.canMask && this.variableData.masked;
+ return !this.canMask && this.variable.masked;
},
maskedState() {
if (this.displayMaskedError) {
@@ -97,9 +110,6 @@ export default {
}
return true;
},
- variableData() {
- return this.variableBeingEdited || this.variable;
- },
modalActionText() {
return this.variableBeingEdited ? __('Update variable') : __('Add variable');
},
@@ -107,7 +117,7 @@ export default {
return this.displayMaskedError ? __('This variable can not be masked.') : '';
},
tokenValidationFeedback() {
- const tokenSpecificFeedback = this.$options.tokens?.[this.variableData.key]?.invalidMessage;
+ const tokenSpecificFeedback = this.$options.tokens?.[this.variable.key]?.invalidMessage;
if (!this.tokenValidationState && tokenSpecificFeedback) {
return tokenSpecificFeedback;
}
@@ -119,10 +129,10 @@ export default {
return true;
}
- const validator = this.$options.tokens?.[this.variableData.key]?.validation;
+ const validator = this.$options.tokens?.[this.variable.key]?.validation;
if (validator) {
- return validator(this.variableData.secret_value);
+ return validator(this.variable.secret_value);
}
return true;
@@ -131,14 +141,7 @@ export default {
return `${this.tokenValidationFeedback} ${this.maskedFeedback}`;
},
variableValidationState() {
- if (
- this.variableData.secret_value === '' ||
- (this.tokenValidationState && this.maskedState)
- ) {
- return true;
- }
-
- return false;
+ return this.variable.secret_value === '' || (this.tokenValidationState && this.maskedState);
},
},
methods: {
@@ -160,7 +163,7 @@ export default {
this.isTipDismissed = true;
},
deleteVarAndClose() {
- this.deleteVariable(this.variableBeingEdited);
+ this.deleteVariable();
this.hideModal();
},
hideModal() {
@@ -169,14 +172,14 @@ export default {
resetModalHandler() {
if (this.variableBeingEdited) {
this.resetEditing();
- } else {
- this.clearModal();
}
+
+ this.clearModal();
this.resetSelectedEnvironment();
},
updateOrAddVariable() {
if (this.variableBeingEdited) {
- this.updateVariable(this.variableBeingEdited);
+ this.updateVariable();
} else {
this.addVariable();
}
@@ -202,16 +205,17 @@ export default {
@shown="setVariableProtectedByDefault"
>
<form>
- <ci-key-field
+ <gl-form-combobox
v-if="glFeatures.ciKeyAutocomplete"
- v-model="variableData.key"
+ v-model="key"
:token-list="$options.tokenList"
+ :label-text="__('Key')"
/>
<gl-form-group v-else :label="__('Key')" label-for="ci-variable-key">
<gl-form-input
id="ci-variable-key"
- v-model="variableData.key"
+ v-model="key"
data-qa-selector="ci_variable_key_field"
/>
</gl-form-group>
@@ -225,11 +229,12 @@ export default {
<gl-form-textarea
id="ci-variable-value"
ref="valueField"
- v-model="variableData.secret_value"
+ v-model="secret_value"
:state="variableValidationState"
rows="3"
max-rows="6"
data-qa-selector="ci_variable_value_field"
+ class="gl-font-monospace!"
/>
</gl-form-group>
@@ -240,11 +245,7 @@ export default {
class="w-50 append-right-15"
:class="{ 'w-100': isGroup }"
>
- <gl-form-select
- id="ci-variable-type"
- v-model="variableData.variable_type"
- :options="typeOptions"
- />
+ <gl-form-select id="ci-variable-type" v-model="variable_type" :options="typeOptions" />
</gl-form-group>
<gl-form-group
@@ -255,7 +256,7 @@ export default {
>
<ci-environments-dropdown
class="w-100"
- :value="variableData.environment_scope"
+ :value="environment_scope"
@selectEnvironment="setEnvironmentScope"
@createClicked="addWildCardScope"
/>
@@ -263,7 +264,7 @@ export default {
</div>
<gl-form-group :label="__('Flags')" label-for="ci-variable-flags">
- <gl-form-checkbox v-model="variableData.protected" class="mb-0">
+ <gl-form-checkbox v-model="protected_variable" class="mb-0">
{{ __('Protect variable') }}
<gl-link target="_blank" :href="protectedEnvironmentVariablesLink">
<gl-icon name="question" :size="12" />
@@ -275,7 +276,7 @@ export default {
<gl-form-checkbox
ref="masked-ci-variable"
- v-model="variableData.masked"
+ v-model="masked"
data-qa-selector="ci_variable_masked_checkbox"
>
{{ __('Mask variable') }}