diff options
Diffstat (limited to 'app/assets/javascripts/packages_and_registries/settings/project')
7 files changed, 257 insertions, 46 deletions
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/cleanup_image_tags.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/cleanup_image_tags.vue new file mode 100644 index 00000000000..72e68aca070 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/cleanup_image_tags.vue @@ -0,0 +1,112 @@ +<script> +import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; +import { isEqual, get, isEmpty } from 'lodash'; +import { + CONTAINER_CLEANUP_POLICY_TITLE, + CONTAINER_CLEANUP_POLICY_DESCRIPTION, + FETCH_SETTINGS_ERROR_MESSAGE, + UNAVAILABLE_FEATURE_TITLE, + UNAVAILABLE_FEATURE_INTRO_TEXT, + UNAVAILABLE_USER_FEATURE_TEXT, + UNAVAILABLE_ADMIN_FEATURE_TEXT, +} from '~/packages_and_registries/settings/project/constants'; +import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; + +import ContainerExpirationPolicyForm from './container_expiration_policy_form.vue'; + +export default { + components: { + GlAlert, + GlSprintf, + GlLink, + ContainerExpirationPolicyForm, + }, + inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries', 'helpPagePath'], + i18n: { + CONTAINER_CLEANUP_POLICY_TITLE, + CONTAINER_CLEANUP_POLICY_DESCRIPTION, + UNAVAILABLE_FEATURE_TITLE, + UNAVAILABLE_FEATURE_INTRO_TEXT, + FETCH_SETTINGS_ERROR_MESSAGE, + }, + apollo: { + containerExpirationPolicy: { + query: expirationPolicyQuery, + variables() { + return { + projectPath: this.projectPath, + }; + }, + update: (data) => data.project?.containerExpirationPolicy, + result({ data }) { + this.workingCopy = { ...get(data, 'project.containerExpirationPolicy', {}) }; + }, + error(e) { + this.fetchSettingsError = e; + }, + }, + }, + data() { + return { + fetchSettingsError: false, + containerExpirationPolicy: null, + workingCopy: {}, + }; + }, + computed: { + isEnabled() { + return this.containerExpirationPolicy || this.enableHistoricEntries; + }, + showDisabledFormMessage() { + return !this.isEnabled && !this.fetchSettingsError; + }, + unavailableFeatureMessage() { + return this.isAdmin ? UNAVAILABLE_ADMIN_FEATURE_TEXT : UNAVAILABLE_USER_FEATURE_TEXT; + }, + isEdited() { + if (isEmpty(this.containerExpirationPolicy) && isEmpty(this.workingCopy)) { + return false; + } + return !isEqual(this.containerExpirationPolicy, this.workingCopy); + }, + }, +}; +</script> + +<template> + <div data-testid="container-expiration-policy-project-settings"> + <h4 data-testid="title">{{ $options.i18n.CONTAINER_CLEANUP_POLICY_TITLE }}</h4> + <p data-testid="description"> + <gl-sprintf :message="$options.i18n.CONTAINER_CLEANUP_POLICY_DESCRIPTION"> + <template #link="{ content }"> + <gl-link :href="helpPagePath">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> + <container-expiration-policy-form + v-if="isEnabled" + v-model="workingCopy" + :is-loading="$apollo.queries.containerExpirationPolicy.loading" + :is-edited="isEdited" + /> + <template v-else> + <gl-alert + v-if="showDisabledFormMessage" + :dismissible="false" + :title="$options.i18n.UNAVAILABLE_FEATURE_TITLE" + variant="tip" + > + {{ $options.i18n.UNAVAILABLE_FEATURE_INTRO_TEXT }} + + <gl-sprintf :message="unavailableFeatureMessage"> + <template #link="{ content }"> + <gl-link :href="adminSettingsPath">{{ content }}</gl-link> + </template> + </gl-sprintf> + </gl-alert> + <gl-alert v-else-if="fetchSettingsError" variant="warning" :dismissible="false"> + <gl-sprintf :message="$options.i18n.FETCH_SETTINGS_ERROR_MESSAGE" /> + </gl-alert> + </template> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue index 1c44d2bc38b..b003b6aeb6b 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy.vue @@ -1,9 +1,11 @@ <script> -import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; -import { isEqual, get, isEmpty } from 'lodash'; +import { GlAlert, GlSprintf, GlLink, GlCard, GlButton } from '@gitlab/ui'; import { CONTAINER_CLEANUP_POLICY_TITLE, CONTAINER_CLEANUP_POLICY_DESCRIPTION, + CONTAINER_CLEANUP_POLICY_EDIT_RULES, + CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION, + CONTAINER_CLEANUP_POLICY_SET_RULES, FETCH_SETTINGS_ERROR_MESSAGE, UNAVAILABLE_FEATURE_TITLE, UNAVAILABLE_FEATURE_INTRO_TEXT, @@ -13,20 +15,29 @@ import { import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql'; import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue'; -import ContainerExpirationPolicyForm from './container_expiration_policy_form.vue'; - export default { components: { SettingsBlock, GlAlert, GlSprintf, GlLink, - ContainerExpirationPolicyForm, + GlCard, + GlButton, }, - inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries', 'helpPagePath'], + inject: [ + 'projectPath', + 'isAdmin', + 'adminSettingsPath', + 'enableHistoricEntries', + 'helpPagePath', + 'cleanupSettingsPath', + ], i18n: { CONTAINER_CLEANUP_POLICY_TITLE, CONTAINER_CLEANUP_POLICY_DESCRIPTION, + CONTAINER_CLEANUP_POLICY_EDIT_RULES, + CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION, + CONTAINER_CLEANUP_POLICY_SET_RULES, UNAVAILABLE_FEATURE_TITLE, UNAVAILABLE_FEATURE_INTRO_TEXT, FETCH_SETTINGS_ERROR_MESSAGE, @@ -40,9 +51,6 @@ export default { }; }, update: (data) => data.project?.containerExpirationPolicy, - result({ data }) { - this.workingCopy = { ...get(data, 'project.containerExpirationPolicy', {}) }; - }, error(e) { this.fetchSettingsError = e; }, @@ -52,29 +60,25 @@ export default { return { fetchSettingsError: false, containerExpirationPolicy: null, - workingCopy: {}, }; }, computed: { - isDisabled() { - return !(this.containerExpirationPolicy || this.enableHistoricEntries); + isCleanupEnabled() { + return this.containerExpirationPolicy?.enabled ?? false; + }, + isEnabled() { + return this.containerExpirationPolicy || this.enableHistoricEntries; }, showDisabledFormMessage() { - return this.isDisabled && !this.fetchSettingsError; + return !this.isEnabled && !this.fetchSettingsError; }, unavailableFeatureMessage() { return this.isAdmin ? UNAVAILABLE_ADMIN_FEATURE_TEXT : UNAVAILABLE_USER_FEATURE_TEXT; }, - isEdited() { - if (isEmpty(this.containerExpirationPolicy) && isEmpty(this.workingCopy)) { - return false; - } - return !isEqual(this.containerExpirationPolicy, this.workingCopy); - }, - }, - methods: { - restoreOriginal() { - this.workingCopy = { ...this.containerExpirationPolicy }; + cleanupRulesButtonText() { + return this.isCleanupEnabled + ? this.$options.i18n.CONTAINER_CLEANUP_POLICY_EDIT_RULES + : this.$options.i18n.CONTAINER_CLEANUP_POLICY_SET_RULES; }, }, }; @@ -93,13 +97,19 @@ export default { </span> </template> <template #default> - <container-expiration-policy-form - v-if="!isDisabled" - v-model="workingCopy" - :is-loading="$apollo.queries.containerExpirationPolicy.loading" - :is-edited="isEdited" - @reset="restoreOriginal" - /> + <gl-card v-if="isEnabled"> + <p data-testid="description"> + {{ $options.i18n.CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION }} + </p> + <gl-button + data-testid="rules-button" + :href="cleanupSettingsPath" + category="secondary" + variant="confirm" + > + {{ cleanupRulesButtonText }} + </gl-button> + </gl-card> <template v-else> <gl-alert v-if="showDisabledFormMessage" diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue index ae2d5f4fbc5..11d8732426d 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/container_expiration_policy_form.vue @@ -1,8 +1,9 @@ <script> import { GlCard, GlButton, GlSprintf } from '@gitlab/ui'; +import { objectToQuery, visitUrl } from '~/lib/utils/url_utility'; import { UPDATE_SETTINGS_ERROR_MESSAGE, - UPDATE_SETTINGS_SUCCESS_MESSAGE, + SHOW_SETUP_SUCCESS_ALERT, SET_CLEANUP_POLICY_BUTTON, KEEP_HEADER_TEXT, KEEP_INFO_TEXT, @@ -37,7 +38,7 @@ export default { ExpirationRunText, }, mixins: [Tracking.mixin()], - inject: ['projectPath'], + inject: ['projectPath', 'projectSettingsPath'], props: { value: { type: Object, @@ -95,10 +96,10 @@ export default { return Object.values(this.localErrors).every((error) => error); }, isSubmitButtonDisabled() { - return !this.fieldsAreValid || this.showLoadingIcon; + return !this.isEdited || !this.fieldsAreValid || this.showLoadingIcon; }, isCancelButtonDisabled() { - return !this.isEdited || this.isLoading || this.mutationLoading; + return this.isLoading || this.mutationLoading; }, isFieldDisabled() { return this.showLoadingIcon || !this.value.enabled; @@ -119,12 +120,6 @@ export default { findDefaultOption(option) { return this.value[option] || this.$options.formOptions[option].find((f) => f.default)?.key; }, - reset() { - this.track('reset_form'); - this.apiErrors = {}; - this.localErrors = {}; - this.$emit('reset'); - }, setApiErrors(response) { this.apiErrors = response.graphQLErrors.reduce((acc, curr) => { curr.extensions.problems.forEach((item) => { @@ -168,7 +163,7 @@ export default { const customError = this.encapsulateError('nameRegex', errorMessage); throw customError; } else { - this.$toast.show(UPDATE_SETTINGS_SUCCESS_MESSAGE); + this.navigateToSettingsWithSuccessAlert(); } }) .catch((error) => { @@ -183,12 +178,17 @@ export default { this.$emit('input', { ...this.value, [model]: newValue }); this.apiErrors[model] = undefined; }, + navigateToSettingsWithSuccessAlert() { + const alertQuery = objectToQuery({ [SHOW_SETUP_SUCCESS_ALERT]: true }); + + visitUrl(`${this.projectSettingsPath}?${alertQuery}`); + }, }, }; </script> <template> - <form ref="form-element" @submit.prevent="submit" @reset.prevent="reset"> + <form @submit.prevent="submit"> <expiration-toggle :value="prefilledForm.enabled" :disabled="showLoadingIcon" @@ -199,7 +199,7 @@ export default { <div class="gl-display-flex gl-mt-7"> <expiration-dropdown - v-model="prefilledForm.cadence" + :value="prefilledForm.cadence" :disabled="isFieldDisabled" :form-options="$options.formOptions.cadence" :label="$options.i18n.CADENCE_LABEL" @@ -231,7 +231,7 @@ export default { </gl-sprintf> </p> <expiration-dropdown - v-model="prefilledForm.keepN" + :value="prefilledForm.keepN" :disabled="isFieldDisabled" :form-options="$options.formOptions.keepN" :label="$options.i18n.KEEP_N_LABEL" @@ -270,7 +270,7 @@ export default { </gl-sprintf> </p> <expiration-dropdown - v-model="prefilledForm.olderThan" + :value="prefilledForm.olderThan" :disabled="isFieldDisabled" :form-options="$options.formOptions.olderThan" :label="$options.i18n.EXPIRATION_SCHEDULE_LABEL" @@ -306,7 +306,7 @@ export default { </gl-button> <gl-button data-testid="cancel-button" - type="reset" + :href="projectSettingsPath" :disabled="isCancelButtonDisabled" class="gl-mr-4" > diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue index 710cfe7b1eb..2c1368262f2 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue +++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue @@ -1,18 +1,57 @@ <script> +import { GlAlert } from '@gitlab/ui'; +import { historyReplaceState } from '~/lib/utils/common_utils'; +import { getParameterByName } from '~/lib/utils/url_utility'; +import { + SHOW_SETUP_SUCCESS_ALERT, + UPDATE_SETTINGS_SUCCESS_MESSAGE, +} from '~/packages_and_registries/settings/project/constants'; import ContainerExpirationPolicy from './container_expiration_policy.vue'; import PackagesCleanupPolicy from './packages_cleanup_policy.vue'; export default { components: { ContainerExpirationPolicy, + GlAlert, PackagesCleanupPolicy, }, inject: ['showContainerRegistrySettings', 'showPackageRegistrySettings'], + i18n: { + UPDATE_SETTINGS_SUCCESS_MESSAGE, + }, + data() { + return { + showAlert: false, + }; + }, + mounted() { + this.checkAlert(); + }, + methods: { + checkAlert() { + const showAlert = getParameterByName(SHOW_SETUP_SUCCESS_ALERT); + + if (showAlert) { + this.showAlert = true; + const cleanUrl = window.location.href.split('?')[0]; + historyReplaceState(cleanUrl); + } + }, + }, }; </script> <template> <div> + <gl-alert + v-if="showAlert" + variant="success" + class="gl-mt-5" + dismissible + @dismiss="showAlert = false" + > + {{ $options.i18n.UPDATE_SETTINGS_SUCCESS_MESSAGE }} + </gl-alert> <packages-cleanup-policy v-if="showPackageRegistrySettings" /> <container-expiration-policy v-if="showContainerRegistrySettings" /> </div> diff --git a/app/assets/javascripts/packages_and_registries/settings/project/constants.js b/app/assets/javascripts/packages_and_registries/settings/project/constants.js index fcb4a8ee297..a9b47cbd343 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/constants.js +++ b/app/assets/javascripts/packages_and_registries/settings/project/constants.js @@ -4,6 +4,13 @@ export const CONTAINER_CLEANUP_POLICY_TITLE = s__(`ContainerRegistry|Clean up im export const CONTAINER_CLEANUP_POLICY_DESCRIPTION = s__( `ContainerRegistry|Save storage space by automatically deleting tags from the container registry and keeping the ones you want. %{linkStart}How does cleanup work?%{linkEnd}`, ); +export const CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION = s__( + 'ContainerRegistry|Set rules to automatically remove unused packages to save storage space.', +); +export const CONTAINER_CLEANUP_POLICY_EDIT_RULES = s__('ContainerRegistry|Edit cleanup rules'); +export const CONTAINER_CLEANUP_POLICY_SET_RULES = s__('ContainerRegistry|Set cleanup rules'); +export const SHOW_SETUP_SUCCESS_ALERT = 'showSetupSuccessAlert'; + export const SET_CLEANUP_POLICY_BUTTON = __('Save changes'); export const UNAVAILABLE_FEATURE_TITLE = s__( `ContainerRegistry|Cleanup policy for tags is disabled`, diff --git a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js index daf1da6eac8..57c8d07e620 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js +++ b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_bundle.js @@ -18,6 +18,7 @@ export default () => { enableHistoricEntries, projectPath, adminSettingsPath, + cleanupSettingsPath, tagsRegexHelpPagePath, helpPagePath, showContainerRegistrySettings, @@ -34,6 +35,7 @@ export default () => { enableHistoricEntries: parseBoolean(enableHistoricEntries), projectPath, adminSettingsPath, + cleanupSettingsPath, tagsRegexHelpPagePath, helpPagePath, showContainerRegistrySettings: parseBoolean(showContainerRegistrySettings), diff --git a/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_cleanup_tags_bundle.js b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_cleanup_tags_bundle.js new file mode 100644 index 00000000000..b1401c448a1 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/settings/project/registry_settings_cleanup_tags_bundle.js @@ -0,0 +1,41 @@ +import { GlToast } from '@gitlab/ui'; +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import Translate from '~/vue_shared/translate'; +import CleanupImageTags from './components/cleanup_image_tags.vue'; +import { apolloProvider } from './graphql/index'; + +Vue.use(GlToast); +Vue.use(Translate); + +export default () => { + const el = document.getElementById('js-registry-settings-cleanup-image-tags'); + if (!el) { + return null; + } + const { + isAdmin, + enableHistoricEntries, + projectPath, + adminSettingsPath, + projectSettingsPath, + tagsRegexHelpPagePath, + helpPagePath, + } = el.dataset; + return new Vue({ + el, + apolloProvider, + provide: { + isAdmin: parseBoolean(isAdmin), + enableHistoricEntries: parseBoolean(enableHistoricEntries), + projectPath, + adminSettingsPath, + projectSettingsPath, + tagsRegexHelpPagePath, + helpPagePath, + }, + render(createElement) { + return createElement(CleanupImageTags, {}); + }, + }); +}; |