diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-19 15:07:35 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-19 15:07:35 +0300 |
commit | e3764d340e2849fccee8c06278d1f5f686edd35b (patch) | |
tree | 23de7fe0eaa58a82c3a72eb8ff4a195e24627eb7 /app/assets/javascripts/registry | |
parent | e3d67bcff7b8bc6a453d0814d404a9a61d97bc0f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/registry')
13 files changed, 286 insertions, 50 deletions
diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue index b2c700b817c..c770fd70260 100644 --- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue +++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue @@ -1,26 +1,23 @@ <script> -import { mapState } from 'vuex'; -import { s__, sprintf } from '~/locale'; +import { mapState, mapActions } from 'vuex'; +import { GlLoadingIcon } from '@gitlab/ui'; +import SettingsForm from './settings_form.vue'; export default { - components: {}, + components: { + GlLoadingIcon, + SettingsForm, + }, computed: { ...mapState({ - helpPagePath: 'helpPagePath', + isLoading: 'isLoading', }), - - helpText() { - return sprintf( - s__( - 'PackageRegistry|Read more about the %{helpLinkStart}Container Registry tag retention policies%{helpLinkEnd}', - ), - { - helpLinkStart: `<a href="${this.helpPagePath}" target="_blank">`, - helpLinkEnd: '</a>', - }, - false, - ); - }, + }, + mounted() { + this.fetchSettings(); + }, + methods: { + ...mapActions(['fetchSettings']), }, }; </script> @@ -28,16 +25,19 @@ export default { <template> <div> <p> - {{ s__('PackageRegistry|Tag retention policies are designed to:') }} + {{ s__('ContainerRegistry|Tag expiration policy is designed to:') }} </p> <ul> - <li>{{ s__('PackageRegistry|Keep and protect the images that matter most.') }}</li> + <li>{{ s__('ContainerRegistry|Keep and protect the images that matter most.') }}</li> <li> {{ - s__("PackageRegistry|Automatically remove extra images that aren't designed to be kept.") + s__( + "ContainerRegistry|Automatically remove extra images that aren't designed to be kept.", + ) }} </li> </ul> - <p ref="help-link" v-html="helpText"></p> + <gl-loading-icon v-if="isLoading" ref="loading-icon" /> + <settings-form v-else ref="settings-form" /> </div> </template> diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue new file mode 100644 index 00000000000..402763e2e21 --- /dev/null +++ b/app/assets/javascripts/registry/settings/components/settings_form.vue @@ -0,0 +1,158 @@ +<script> +import { mapActions } from 'vuex'; +import { GlFormGroup, GlToggle, GlFormSelect, GlFormTextarea, GlButton } from '@gitlab/ui'; +import { s__, __, sprintf } from '~/locale'; +import { NAME_REGEX_LENGTH } from '../constants'; +import { mapComputed } from '~/vuex_shared/bindings'; + +export default { + components: { + GlFormGroup, + GlToggle, + GlFormSelect, + GlFormTextarea, + GlButton, + }, + labelsConfig: { + cols: 3, + align: 'right', + }, + computed: { + ...mapComputed('settings', 'updateSettings', [ + 'enabled', + 'cadence', + 'older_than', + 'keep_n', + 'name_regex', + ]), + 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', + ), + { + codeStart: '<code>', + codeEnd: '</code>', + }, + false, + ); + }, + nameRegexPlaceholder() { + return '.*'; + }, + nameRegexState() { + return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null; + }, + formIsValid() { + return this.nameRegexState === false; + }, + }, + methods: { + ...mapActions(['resetSettings', 'saveSettings']), + }, +}; +</script> + +<template> + <div class="card"> + <form ref="form-element" @submit.prevent="saveSettings" @reset.prevent="resetSettings"> + <div class="card-header"> + {{ s__('ContainerRegistry|Tag expiration policy') }} + </div> + <div class="card-body"> + <gl-form-group + id="expiration-policy-toggle-group" + :label-cols="$options.labelsConfig.cols" + :label-align="$options.labelsConfig.align" + label-for="expiration-policy-toggle" + :label="s__('ContainerRegistry|Expiration policy:')" + > + <div class="d-flex align-items-start"> + <gl-toggle id="expiration-policy-toggle" v-model="enabled" /> + <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span> + </div> + </gl-form-group> + + <gl-form-group + id="expiration-policy-interval-group" + :label-cols="$options.labelsConfig.cols" + :label-align="$options.labelsConfig.align" + label-for="expiration-policy-interval" + :label="s__('ContainerRegistry|Expiration interval:')" + > + <gl-form-select id="expiration-policy-interval" v-model="older_than"> + <option value="1">{{ __('Option 1') }}</option> + <option value="2">{{ __('Option 2') }}</option> + </gl-form-select> + </gl-form-group> + + <gl-form-group + id="expiration-policy-schedule-group" + :label-cols="$options.labelsConfig.cols" + :label-align="$options.labelsConfig.align" + label-for="expiration-policy-schedule" + :label="s__('ContainerRegistry|Expiration schedule:')" + > + <gl-form-select id="expiration-policy-schedule" v-model="cadence"> + <option value="1">{{ __('Option 1') }}</option> + <option value="2">{{ __('Option 2') }}</option> + </gl-form-select> + </gl-form-group> + + <gl-form-group + id="expiration-policy-latest-group" + :label-cols="$options.labelsConfig.cols" + :label-align="$options.labelsConfig.align" + label-for="expiration-policy-latest" + :label="s__('ContainerRegistry|Expiration latest:')" + > + <gl-form-select id="expiration-policy-latest" v-model="keep_n"> + <option value="1">{{ __('Option 1') }}</option> + <option value="2">{{ __('Option 2') }}</option> + </gl-form-select> + </gl-form-group> + + <gl-form-group + id="expiration-policy-name-matching-group" + :label-cols="$options.labelsConfig.cols" + :label-align="$options.labelsConfig.align" + label-for="expiration-policy-name-matching" + :label="s__('ContainerRegistry|Expire Docker tags with name matching:')" + :state="nameRegexState" + :invalid-feedback=" + s__('ContainerRegistry|The value of this input should be less than 255 characters') + " + > + <gl-form-textarea + id="expiration-policy-name-matching" + v-model="name_regex" + :placeholder="nameRegexPlaceholder" + :state="nameRegexState" + trim + /> + <template #description> + <span ref="regex-description" v-html="regexHelpText"></span> + </template> + </gl-form-group> + </div> + <div class="card-footer text-right"> + <gl-button ref="cancel-button" type="reset">{{ __('Cancel') }}</gl-button> + <gl-button ref="save-button" type="submit" :disabled="formIsValid" variant="success"> + {{ __('Save Expiration Policy') }} + </gl-button> + </div> + </form> + </div> +</template> diff --git a/app/assets/javascripts/registry/settings/constants.js b/app/assets/javascripts/registry/settings/constants.js new file mode 100644 index 00000000000..c0dac466b29 --- /dev/null +++ b/app/assets/javascripts/registry/settings/constants.js @@ -0,0 +1,15 @@ +import { s__ } from '~/locale'; + +export const FETCH_SETTINGS_ERROR_MESSAGE = s__( + 'ContainerRegistry|Something went wrong while fetching the expiration policy.', +); + +export const UPDATE_SETTINGS_ERROR_MESSAGE = s__( + 'ContainerRegistry|Something went wrong while updating the expiration policy.', +); + +export const UPDATE_SETTINGS_SUCCESS_MESSAGE = s__( + 'ContainerRegistry|Expiration policy successfully saved.', +); + +export const NAME_REGEX_LENGTH = 255; diff --git a/app/assets/javascripts/registry/settings/registry_settings_bundle.js b/app/assets/javascripts/registry/settings/registry_settings_bundle.js index 2938178ea86..927b6059884 100644 --- a/app/assets/javascripts/registry/settings/registry_settings_bundle.js +++ b/app/assets/javascripts/registry/settings/registry_settings_bundle.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; -import store from './stores/'; +import store from './store/'; import RegistrySettingsApp from './components/registry_settings_app.vue'; Vue.use(Translate); diff --git a/app/assets/javascripts/registry/settings/store/actions.js b/app/assets/javascripts/registry/settings/store/actions.js new file mode 100644 index 00000000000..b161373dd0a --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/actions.js @@ -0,0 +1,40 @@ +import Api from '~/api'; +import createFlash from '~/flash'; +import { + FETCH_SETTINGS_ERROR_MESSAGE, + UPDATE_SETTINGS_ERROR_MESSAGE, + UPDATE_SETTINGS_SUCCESS_MESSAGE, +} from '../constants'; +import * as types from './mutation_types'; + +export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data); +export const updateSettings = ({ commit }, data) => commit(types.UPDATE_SETTINGS, data); +export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING); +export const receiveSettingsSuccess = ({ commit }, data = {}) => commit(types.SET_SETTINGS, data); +export const receiveSettingsError = () => createFlash(FETCH_SETTINGS_ERROR_MESSAGE); +export const updateSettingsError = () => createFlash(UPDATE_SETTINGS_ERROR_MESSAGE); +export const resetSettings = ({ commit }) => commit(types.RESET_SETTINGS); + +export const fetchSettings = ({ dispatch, state }) => { + dispatch('toggleLoading'); + return Api.project(state.projectId) + .then(({ tag_expiration_policies }) => + dispatch('receiveSettingsSuccess', tag_expiration_policies), + ) + .catch(() => dispatch('receiveSettingsError')) + .finally(() => dispatch('toggleLoading')); +}; + +export const saveSettings = ({ dispatch, state }) => { + dispatch('toggleLoading'); + return Api.updateProject(state.projectId, { tag_expiration_policies: state.settings }) + .then(({ tag_expiration_policies }) => { + dispatch('receiveSettingsSuccess', tag_expiration_policies); + createFlash(UPDATE_SETTINGS_SUCCESS_MESSAGE); + }) + .catch(() => dispatch('updateSettingsError')) + .finally(() => dispatch('toggleLoading')); +}; + +// prevent babel-plugin-rewire from generating an invalid default during karma tests +export default () => {}; diff --git a/app/assets/javascripts/registry/settings/stores/index.js b/app/assets/javascripts/registry/settings/store/index.js index 91a35aac149..91a35aac149 100644 --- a/app/assets/javascripts/registry/settings/stores/index.js +++ b/app/assets/javascripts/registry/settings/store/index.js diff --git a/app/assets/javascripts/registry/settings/store/mutation_types.js b/app/assets/javascripts/registry/settings/store/mutation_types.js new file mode 100644 index 00000000000..db499ffa761 --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/mutation_types.js @@ -0,0 +1,5 @@ +export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; +export const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; +export const TOGGLE_LOADING = 'TOGGLE_LOADING'; +export const SET_SETTINGS = 'SET_SETTINGS'; +export const RESET_SETTINGS = 'RESET_SETTINGS'; diff --git a/app/assets/javascripts/registry/settings/store/mutations.js b/app/assets/javascripts/registry/settings/store/mutations.js new file mode 100644 index 00000000000..b8384fd4a45 --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/mutations.js @@ -0,0 +1,20 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_INITIAL_STATE](state, initialState) { + state.projectId = initialState.projectId; + }, + [types.UPDATE_SETTINGS](state, settings) { + state.settings = { ...state.settings, ...settings }; + }, + [types.SET_SETTINGS](state, settings) { + state.settings = settings; + state.original = Object.freeze(settings); + }, + [types.RESET_SETTINGS](state) { + state.settings = { ...state.original }; + }, + [types.TOGGLE_LOADING](state) { + state.isLoading = !state.isLoading; + }, +}; diff --git a/app/assets/javascripts/registry/settings/store/state.js b/app/assets/javascripts/registry/settings/store/state.js new file mode 100644 index 00000000000..c3a26083c9f --- /dev/null +++ b/app/assets/javascripts/registry/settings/store/state.js @@ -0,0 +1,26 @@ +export default () => ({ + /* + * Project Id used to build the API call + */ + projectId: '', + /* + * Boolean to determine if the UI is loading data from the API + */ + isLoading: false, + /* + * This contains the data shown and manipulated in the UI + * Has the following structure: + * { + * enabled: Boolean + * cadence: String, + * older_than: String, + * keep_n: String, + * name_regex: String + * } + */ + settings: {}, + /* + * Same structure as settings, above but Frozen object and used only in case the user clicks 'cancel' + */ + original: {}, +}); diff --git a/app/assets/javascripts/registry/settings/stores/actions.js b/app/assets/javascripts/registry/settings/stores/actions.js deleted file mode 100644 index f2c469d4edb..00000000000 --- a/app/assets/javascripts/registry/settings/stores/actions.js +++ /dev/null @@ -1,6 +0,0 @@ -import * as types from './mutation_types'; - -export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data); - -// to avoid eslint error until more actions are added to the store -export default () => {}; diff --git a/app/assets/javascripts/registry/settings/stores/mutation_types.js b/app/assets/javascripts/registry/settings/stores/mutation_types.js deleted file mode 100644 index 8a0f519eabd..00000000000 --- a/app/assets/javascripts/registry/settings/stores/mutation_types.js +++ /dev/null @@ -1,4 +0,0 @@ -export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; - -// to avoid eslint error until more actions are added to the store -export default () => {}; diff --git a/app/assets/javascripts/registry/settings/stores/mutations.js b/app/assets/javascripts/registry/settings/stores/mutations.js deleted file mode 100644 index 4f32e11ed52..00000000000 --- a/app/assets/javascripts/registry/settings/stores/mutations.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as types from './mutation_types'; - -export default { - [types.SET_INITIAL_STATE](state, initialState) { - state.helpPagePath = initialState.helpPagePath; - state.registrySettingsEndpoint = initialState.registrySettingsEndpoint; - }, -}; diff --git a/app/assets/javascripts/registry/settings/stores/state.js b/app/assets/javascripts/registry/settings/stores/state.js deleted file mode 100644 index 4c0439458b6..00000000000 --- a/app/assets/javascripts/registry/settings/stores/state.js +++ /dev/null @@ -1,10 +0,0 @@ -export default () => ({ - /* - * Help page path to generate the link - */ - helpPagePath: '', - /* - * Settings endpoint to call to fetch and update the settings - */ - registrySettingsEndpoint: '', -}); |