summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/registry
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-19 12:07:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-19 12:07:35 +0000
commite3764d340e2849fccee8c06278d1f5f686edd35b (patch)
tree23de7fe0eaa58a82c3a72eb8ff4a195e24627eb7 /app/assets/javascripts/registry
parente3d67bcff7b8bc6a453d0814d404a9a61d97bc0f (diff)
downloadgitlab-ce-e3764d340e2849fccee8c06278d1f5f686edd35b.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/registry')
-rw-r--r--app/assets/javascripts/registry/settings/components/registry_settings_app.vue42
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue158
-rw-r--r--app/assets/javascripts/registry/settings/constants.js15
-rw-r--r--app/assets/javascripts/registry/settings/registry_settings_bundle.js2
-rw-r--r--app/assets/javascripts/registry/settings/store/actions.js40
-rw-r--r--app/assets/javascripts/registry/settings/store/index.js (renamed from app/assets/javascripts/registry/settings/stores/index.js)0
-rw-r--r--app/assets/javascripts/registry/settings/store/mutation_types.js5
-rw-r--r--app/assets/javascripts/registry/settings/store/mutations.js20
-rw-r--r--app/assets/javascripts/registry/settings/store/state.js26
-rw-r--r--app/assets/javascripts/registry/settings/stores/actions.js6
-rw-r--r--app/assets/javascripts/registry/settings/stores/mutation_types.js4
-rw-r--r--app/assets/javascripts/registry/settings/stores/mutations.js8
-rw-r--r--app/assets/javascripts/registry/settings/stores/state.js10
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: '',
-});