diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-14 12:09:14 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-14 12:09:14 +0300 |
commit | 4ab67d65296979ba6f5fca3142b86460b748590e (patch) | |
tree | cbaa126a09126f2e43ba006c30dc8175858573e3 /app | |
parent | 90fa047c0dbb0a5e97c384fa6c45991c04acba5f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
26 files changed, 738 insertions, 180 deletions
diff --git a/app/assets/javascripts/lib/utils/webpack.js b/app/assets/javascripts/lib/utils/webpack.js index 07a4d2deb0b..a88f1bd82fc 100644 --- a/app/assets/javascripts/lib/utils/webpack.js +++ b/app/assets/javascripts/lib/utils/webpack.js @@ -11,10 +11,4 @@ export function resetServiceWorkersPublicPath() { const relativeRootPath = (gon && gon.relative_url_root) || ''; const webpackAssetPath = joinPaths(relativeRootPath, '/assets/webpack/'); __webpack_public_path__ = webpackAssetPath; // eslint-disable-line babel/camelcase - - // monaco-editor-webpack-plugin currently (incorrectly) references the - // public path as a property of `window`. Once this is fixed upstream we - // can remove this line - // see: https://github.com/Microsoft/monaco-editor-webpack-plugin/pull/63 - window.__webpack_public_path__ = webpackAssetPath; // eslint-disable-line } diff --git a/app/assets/javascripts/packages/list/components/package_title.vue b/app/assets/javascripts/packages/list/components/package_title.vue index 6176e15ffd4..426ad150ea9 100644 --- a/app/assets/javascripts/packages/list/components/package_title.vue +++ b/app/assets/javascripts/packages/list/components/package_title.vue @@ -11,25 +11,25 @@ export default { MetadataItem, }, props: { - packagesCount: { + count: { type: Number, required: false, default: null, }, - packageHelpUrl: { + helpUrl: { type: String, required: true, }, }, computed: { showPackageCount() { - return Number.isInteger(this.packagesCount); + return Number.isInteger(this.count); }, packageAmountText() { - return n__(`%d Package`, `%d Packages`, this.packagesCount); + return n__(`%d Package`, `%d Packages`, this.count); }, infoMessages() { - return [{ text: LIST_INTRO_TEXT, link: this.packageHelpUrl }]; + return [{ text: LIST_INTRO_TEXT, link: this.helpUrl }]; }, }, i18n: { diff --git a/app/assets/javascripts/packages/list/components/packages_list_app.vue b/app/assets/javascripts/packages/list/components/packages_list_app.vue index b9d922bf1cf..4c5fb0ee7c9 100644 --- a/app/assets/javascripts/packages/list/components/packages_list_app.vue +++ b/app/assets/javascripts/packages/list/components/packages_list_app.vue @@ -8,8 +8,6 @@ import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants'; import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants'; import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils'; import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '../constants'; -import PackageSearch from './package_search.vue'; -import PackageTitle from './package_title.vue'; import PackageList from './packages_list.vue'; export default { @@ -18,8 +16,38 @@ export default { GlLink, GlSprintf, PackageList, - PackageTitle, - PackageSearch, + PackageTitle: () => + import(/* webpackChunkName: 'package_registry_components' */ './package_title.vue'), + PackageSearch: () => + import(/* webpackChunkName: 'package_registry_components' */ './package_search.vue'), + InfrastructureTitle: () => + import( + /* webpackChunkName: 'infrastructure_registry_components' */ '~/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue' + ), + InfrastructureSearch: () => + import( + /* webpackChunkName: 'infrastructure_registry_components' */ '~/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue' + ), + }, + inject: { + titleComponent: { + from: 'titleComponent', + default: 'PackageTitle', + }, + searchComponent: { + from: 'searchComponent', + default: 'PackageSearch', + }, + emptyPageTitle: { + from: 'emptyPageTitle', + default: s__('PackageRegistry|There are no packages yet'), + }, + noResultsText: { + from: 'noResultsText', + default: s__( + 'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.', + ), + }, }, computed: { ...mapState({ @@ -38,7 +66,7 @@ export default { emptyStateTitle() { return this.emptySearch - ? s__('PackageRegistry|There are no packages yet') + ? this.emptyPageTitle : s__('PackageRegistry|Sorry, your filter produced no results'); }, }, @@ -77,24 +105,21 @@ export default { }, i18n: { widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'), - noResults: s__( - 'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.', - ), }, }; </script> <template> <div> - <package-title :package-help-url="packageHelpUrl" :packages-count="packagesCount" /> - <package-search @update="requestPackagesList" /> + <component :is="titleComponent" :help-url="packageHelpUrl" :count="packagesCount" /> + <component :is="searchComponent" @update="requestPackagesList" /> <package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest"> <template #empty-state> <gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration"> <template #description> <gl-sprintf v-if="!emptySearch" :message="$options.i18n.widenFilters" /> - <gl-sprintf v-else :message="$options.i18n.noResults"> + <gl-sprintf v-else :message="noResultsText"> <template #noPackagesLink="{ content }"> <gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link> </template> diff --git a/app/assets/javascripts/packages/list/packages_list_app_bundle.js b/app/assets/javascripts/packages/list/packages_list_app_bundle.js index 58b09c1ebd1..2911cf70a33 100644 --- a/app/assets/javascripts/packages/list/packages_list_app_bundle.js +++ b/app/assets/javascripts/packages/list/packages_list_app_bundle.js @@ -1,11 +1,8 @@ import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createDefaultClient from '~/lib/graphql'; import Translate from '~/vue_shared/translate'; import PackagesListApp from './components/packages_list_app.vue'; import { createStore } from './stores'; -Vue.use(VueApollo); Vue.use(Translate); export default () => { @@ -13,14 +10,9 @@ export default () => { const store = createStore(); store.dispatch('setInitialState', el.dataset); - const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), - }); - return new Vue({ el, store, - apolloProvider, components: { PackagesListApp, }, diff --git a/app/assets/javascripts/packages/shared/components/package_icon_and_name.vue b/app/assets/javascripts/packages/shared/components/package_icon_and_name.vue new file mode 100644 index 00000000000..105f7bbe132 --- /dev/null +++ b/app/assets/javascripts/packages/shared/components/package_icon_and_name.vue @@ -0,0 +1,17 @@ +<script> +import { GlIcon } from '@gitlab/ui'; + +export default { + name: 'PackageIconAndName', + components: { + GlIcon, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon name="package" class="gl-ml-3 gl-mr-2" /> + <span><slot></slot></span> + </div> +</template> diff --git a/app/assets/javascripts/packages/shared/components/package_list_row.vue b/app/assets/javascripts/packages/shared/components/package_list_row.vue index 172b356227a..4de4c191e51 100644 --- a/app/assets/javascripts/packages/shared/components/package_list_row.vue +++ b/app/assets/javascripts/packages/shared/components/package_list_row.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlIcon, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; +import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { getPackageTypeLabel } from '../utils'; @@ -11,7 +11,6 @@ export default { name: 'PackageListRow', components: { GlButton, - GlIcon, GlLink, GlSprintf, GlTruncate, @@ -19,11 +18,23 @@ export default { PackagePath, PublishMethod, ListItem, + PackageIconAndName: () => + import(/* webpackChunkName: 'package_registry_components' */ './package_icon_and_name.vue'), + InfrastructureIconAndName: () => + import( + /* webpackChunkName: 'infrastructure_registry_components' */ '~/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue' + ), }, directives: { GlTooltip: GlTooltipDirective, }, mixins: [timeagoMixin], + inject: { + iconComponent: { + from: 'iconComponent', + default: 'PackageIconAndName', + }, + }, props: { packageEntity: { type: Object, @@ -94,10 +105,9 @@ export default { </gl-sprintf> </div> - <div v-if="showPackageType" class="d-flex align-items-center" data-testid="package-type"> - <gl-icon name="package" class="gl-ml-3 gl-mr-2" /> - <span>{{ packageType }}</span> - </div> + <component :is="iconComponent" v-if="showPackageType"> + {{ packageType }} + </component> <package-path v-if="hasProjectLink" :path="packageEntity.project_path" /> </div> diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue new file mode 100644 index 00000000000..3100a1a7296 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue @@ -0,0 +1,17 @@ +<script> +import { GlIcon } from '@gitlab/ui'; + +export default { + name: 'InfrastructureIconAndName', + components: { + GlIcon, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-align-items-center"> + <gl-icon name="infrastructure-registry" class="gl-ml-3 gl-mr-2" /> + <span>{{ s__('InfrastructureRegistry|Terraform') }}</span> + </div> +</template> diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue new file mode 100644 index 00000000000..2ed17208404 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue @@ -0,0 +1,45 @@ +<script> +import { mapState, mapActions } from 'vuex'; +import { LIST_KEY_PACKAGE_TYPE } from '~/packages/list/constants'; +import getTableHeaders from '~/packages/list/utils'; +import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; +import UrlSync from '~/vue_shared/components/url_sync.vue'; + +export default { + components: { RegistrySearch, UrlSync }, + computed: { + ...mapState({ + isGroupPage: (state) => state.config.isGroupPage, + sorting: (state) => state.sorting, + filter: (state) => state.filter, + }), + sortableFields() { + return getTableHeaders(this.isGroupPage).filter((h) => h.orderBy !== LIST_KEY_PACKAGE_TYPE); + }, + }, + methods: { + ...mapActions(['setSorting', 'setFilter']), + updateSorting(newValue) { + this.setSorting(newValue); + this.$emit('update'); + }, + }, +}; +</script> + +<template> + <url-sync> + <template #default="{ updateQuery }"> + <registry-search + :filter="filter" + :sorting="sorting" + :tokens="[]" + :sortable-fields="sortableFields" + @sorting:changed="updateSorting" + @filter:changed="setFilter" + @filter:submit="$emit('update')" + @query:changed="updateQuery" + /> + </template> + </url-sync> +</template> diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue new file mode 100644 index 00000000000..2a479c65d0c --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue @@ -0,0 +1,53 @@ +<script> +import { s__, n__ } from '~/locale'; +import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; +import TitleArea from '~/vue_shared/components/registry/title_area.vue'; + +export default { + name: 'InfrastructureTitle', + components: { + TitleArea, + MetadataItem, + }, + props: { + count: { + type: Number, + required: false, + default: null, + }, + helpUrl: { + type: String, + required: true, + }, + }, + computed: { + showModuleCount() { + return Number.isInteger(this.count); + }, + moduleAmountText() { + return n__(`%d Module`, `%d Modules`, this.count); + }, + infoMessages() { + return [{ text: this.$options.i18n.LIST_INTRO_TEXT, link: this.helpUrl }]; + }, + }, + i18n: { + LIST_TITLE_TEXT: s__('InfrastructureRegistry|Infrastructure Registry'), + LIST_INTRO_TEXT: s__( + 'InfrastructureRegistry|Publish and share your modules. %{docLinkStart}More information%{docLinkEnd}', + ), + }, +}; +</script> + +<template> + <title-area :title="$options.i18n.LIST_TITLE_TEXT" :info-messages="infoMessages"> + <template #metadata-amount> + <metadata-item + v-if="showModuleCount" + icon="infrastructure-registry" + :text="moduleAmountText" + /> + </template> + </title-area> +</template> diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js new file mode 100644 index 00000000000..88ee8a4200e --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import { s__ } from '~/locale'; +import PackagesListApp from '~/packages/list/components/packages_list_app.vue'; +import { createStore } from '~/packages/list/stores'; +import Translate from '~/vue_shared/translate'; + +Vue.use(Translate); + +export default () => { + const el = document.getElementById('js-vue-packages-list'); + const store = createStore(); + store.dispatch('setInitialState', el.dataset); + + return new Vue({ + el, + store, + components: { + PackagesListApp, + }, + provide: { + titleComponent: 'InfrastructureTitle', + searchComponent: 'InfrastructureSearch', + iconComponent: 'InfrastructureIconAndName', + emptyPageTitle: s__('InfrastructureRegistry|You have no Terraform modules in your project'), + noResultsText: s__( + 'InfrastructureRegistry|Terraform modules are the main way to package and reuse resource configurations with Terraform. Learn more about how to %{noPackagesLinkStart}create Terraform modules%{noPackagesLinkEnd} in GitLab.', + ), + }, + render(createElement) { + return createElement('packages-list-app'); + }, + }); +}; diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js index 2732fc191be..6b7bfbf217d 100644 --- a/app/assets/javascripts/pages/admin/admin.js +++ b/app/assets/javascripts/pages/admin/admin.js @@ -1,16 +1,6 @@ import $ from 'jquery'; import { refreshCurrentPage } from '../../lib/utils/url_utility'; -function showDenylistType() { - if ($('input[name="denylist_type"]:checked').val() === 'file') { - $('.js-denylist-file').show(); - $('.js-denylist-raw').hide(); - } else { - $('.js-denylist-file').hide(); - $('.js-denylist-raw').show(); - } -} - export default function adminInit() { $('input#user_force_random_password').on('change', function randomPasswordClick() { const $elems = $('#user_password, #user_password_confirmation'); @@ -27,7 +17,4 @@ export default function adminInit() { }); $('li.project_member, li.group_member').on('ajax:success', refreshCurrentPage); - - $("input[name='denylist_type']").on('click', showDenylistType); - showDenylistType(); } diff --git a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_checkbox.vue b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_checkbox.vue new file mode 100644 index 00000000000..2217792d7f3 --- /dev/null +++ b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_checkbox.vue @@ -0,0 +1,50 @@ +<script> +import { GlFormCheckbox } from '@gitlab/ui'; + +export default { + components: { + GlFormCheckbox, + }, + props: { + name: { + type: String, + required: true, + }, + helpText: { + type: String, + required: false, + default: '', + }, + label: { + type: String, + required: true, + }, + value: { + type: Boolean, + required: true, + }, + dataQaSelector: { + type: String, + required: false, + default: '', + }, + }, +}; +</script> + +<template> + <div> + <input :name="name" type="hidden" :value="value ? '1' : '0'" data-testid="input" /> + + <gl-form-checkbox + :checked="value" + :data-qa-selector="dataQaSelector" + @input="$emit('input', $event)" + > + <span data-testid="label">{{ label }}</span> + <template v-if="helpText" #help> + <span data-testid="helpText">{{ helpText }}</span> + </template> + </gl-form-checkbox> + </div> +</template> diff --git a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue new file mode 100644 index 00000000000..e9091d30523 --- /dev/null +++ b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue @@ -0,0 +1,331 @@ +<script> +import { + GlButton, + GlFormGroup, + GlFormInput, + GlFormRadio, + GlFormRadioGroup, + GlSprintf, + GlLink, +} from '@gitlab/ui'; +import csrf from '~/lib/utils/csrf'; +import { s__, sprintf } from '~/locale'; +import SignupCheckbox from './signup_checkbox.vue'; + +const DENYLIST_TYPE_RAW = 'raw'; +const DENYLIST_TYPE_FILE = 'file'; + +export default { + csrf, + DENYLIST_TYPE_RAW, + DENYLIST_TYPE_FILE, + components: { + GlButton, + GlFormGroup, + GlFormInput, + GlFormRadio, + GlFormRadioGroup, + GlSprintf, + GlLink, + SignupCheckbox, + }, + inject: [ + 'host', + 'settingsPath', + 'signupEnabled', + 'requireAdminApprovalAfterUserSignup', + 'sendUserConfirmationEmail', + 'minimumPasswordLength', + 'minimumPasswordLengthMin', + 'minimumPasswordLengthMax', + 'minimumPasswordLengthHelpLink', + 'domainAllowlistRaw', + 'newUserSignupsCap', + 'domainDenylistEnabled', + 'denylistTypeRawSelected', + 'domainDenylistRaw', + 'emailRestrictionsEnabled', + 'supportedSyntaxLinkUrl', + 'emailRestrictions', + 'afterSignUpText', + ], + data() { + return { + form: { + signupEnabled: this.signupEnabled, + requireAdminApproval: this.requireAdminApprovalAfterUserSignup, + sendConfirmationEmail: this.sendUserConfirmationEmail, + minimumPasswordLength: this.minimumPasswordLength, + minimumPasswordLengthMin: this.minimumPasswordLengthMin, + minimumPasswordLengthMax: this.minimumPasswordLengthMax, + minimumPasswordLengthHelpLink: this.minimumPasswordLengthHelpLink, + domainAllowlistRaw: this.domainAllowlistRaw, + userCap: this.newUserSignupsCap, + domainDenylistEnabled: this.domainDenylistEnabled, + denylistType: this.denylistTypeRawSelected + ? this.$options.DENYLIST_TYPE_RAW + : this.$options.DENYLIST_TYPE_FILE, + domainDenylistRaw: this.domainDenylistRaw, + emailRestrictionsEnabled: this.emailRestrictionsEnabled, + supportedSyntaxLinkUrl: this.supportedSyntaxLinkUrl, + emailRestrictions: this.emailRestrictions, + afterSignUpText: this.afterSignUpText, + }, + }; + }, + computed: { + signupEnabledHelpText() { + const text = sprintf( + s__( + 'ApplicationSettings|When enabled, any user visiting %{host} will be able to create an account.', + ), + { + host: this.host, + }, + ); + + return text; + }, + requireAdminApprovalHelpText() { + const text = sprintf( + s__( + 'ApplicationSettings|When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled.', + ), + { + host: this.host, + }, + ); + + return text; + }, + }, + methods: { + submitButtonHandler() { + this.$refs.form.submit(); + }, + }, + i18n: { + buttonText: s__('ApplicationSettings|Save changes'), + signupEnabledLabel: s__('ApplicationSettings|Sign-up enabled'), + requireAdminApprovalLabel: s__('ApplicationSettings|Require admin approval for new sign-ups'), + sendConfirmationEmailLabel: s__('ApplicationSettings|Send confirmation email on sign-up'), + minimumPasswordLengthLabel: s__( + 'ApplicationSettings|Minimum password length (number of characters)', + ), + domainAllowListLabel: s__('ApplicationSettings|Allowed domains for sign-ups'), + domainAllowListDescription: s__( + 'ApplicationSettings|ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com', + ), + userCapLabel: s__('ApplicationSettings|User cap'), + userCapDescription: s__( + 'ApplicationSettings|Once the instance reaches the user cap, any user who is added or requests access will have to be approved by an admin. Leave the field empty for unlimited.', + ), + domainDenyListGroupLabel: s__('ApplicationSettings|Domain denylist'), + domainDenyListLabel: s__('ApplicationSettings|Enable domain denylist for sign ups'), + domainDenyListTypeFileLabel: s__('ApplicationSettings|Upload denylist file'), + domainDenyListTypeRawLabel: s__('ApplicationSettings|Enter denylist manually'), + domainDenyListFileLabel: s__('ApplicationSettings|Denylist file'), + domainDenyListFileDescription: s__( + 'ApplicationSettings|Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.', + ), + domainDenyListListLabel: s__('ApplicationSettings|Denied domains for sign-ups'), + domainDenyListListDescription: s__( + 'ApplicationSettings|Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com', + ), + domainPlaceholder: s__('ApplicationSettings|domain.com'), + emailRestrictionsEnabledGroupLabel: s__('ApplicationSettings|Email restrictions'), + emailRestrictionsEnabledLabel: s__( + 'ApplicationSettings|Enable email restrictions for sign ups', + ), + emailRestrictionsGroupLabel: s__('ApplicationSettings|Email restrictions for sign-ups'), + afterSignUpTextGroupLabel: s__('ApplicationSettings|After sign up text'), + afterSignUpTextGroupDescription: s__('ApplicationSettings|Markdown enabled'), + }, +}; +</script> + +<template> + <form + ref="form" + accept-charset="UTF-8" + data-testid="form" + method="post" + :action="settingsPath" + enctype="multipart/form-data" + > + <input type="hidden" name="utf8" value="✓" /> + <input type="hidden" name="_method" value="patch" /> + <input type="hidden" name="authenticity_token" :value="$options.csrf.token" /> + + <section class="gl-mb-8"> + <signup-checkbox + v-model="form.signupEnabled" + class="gl-mb-5" + name="application_setting[signup_enabled]" + :help-text="signupEnabledHelpText" + :label="$options.i18n.signupEnabledLabel" + data-qa-selector="signup_enabled_checkbox" + /> + + <signup-checkbox + v-model="form.requireAdminApproval" + class="gl-mb-5" + name="application_setting[require_admin_approval_after_user_signup]" + :help-text="requireAdminApprovalHelpText" + :label="$options.i18n.requireAdminApprovalLabel" + data-qa-selector="require_admin_approval_after_user_signup_checkbox" + /> + + <signup-checkbox + v-model="form.sendConfirmationEmail" + class="gl-mb-5" + name="application_setting[send_user_confirmation_email]" + :label="$options.i18n.sendConfirmationEmailLabel" + /> + + <gl-form-group + :label="$options.i18n.userCapLabel" + :description="$options.i18n.userCapDescription" + > + <gl-form-input + v-model="form.userCap" + type="text" + name="application_setting[new_user_signups_cap]" + /> + </gl-form-group> + + <gl-form-group :label="$options.i18n.minimumPasswordLengthLabel"> + <gl-form-input + v-model="form.minimumPasswordLength" + :min="form.minimumPasswordLengthMin" + :max="form.minimumPasswordLengthMax" + type="number" + name="application_setting[minimum_password_length]" + /> + + <gl-sprintf + :message=" + s__( + 'ApplicationSettings|See GitLab\'s %{linkStart}Password Policy Guidelines%{linkEnd}', + ) + " + > + <template #link="{ content }"> + <gl-link :href="form.minimumPasswordLengthHelpLink" target="_blank">{{ + content + }}</gl-link> + </template> + </gl-sprintf> + </gl-form-group> + + <gl-form-group + :description="$options.i18n.domainAllowListDescription" + :label="$options.i18n.domainAllowListLabel" + > + <textarea + v-model="form.domainAllowlistRaw" + :placeholder="$options.i18n.domainPlaceholder" + rows="8" + class="form-control gl-form-input" + name="application_setting[domain_allowlist_raw]" + ></textarea> + </gl-form-group> + + <gl-form-group :label="$options.i18n.domainDenyListGroupLabel"> + <signup-checkbox + v-model="form.domainDenylistEnabled" + name="application_setting[domain_denylist_enabled]" + :label="$options.i18n.domainDenyListLabel" + /> + </gl-form-group> + + <gl-form-radio-group v-model="form.denylistType" name="denylist_type" class="gl-mb-5"> + <gl-form-radio :value="$options.DENYLIST_TYPE_FILE">{{ + $options.i18n.domainDenyListTypeFileLabel + }}</gl-form-radio> + <gl-form-radio :value="$options.DENYLIST_TYPE_RAW">{{ + $options.i18n.domainDenyListTypeRawLabel + }}</gl-form-radio> + </gl-form-radio-group> + + <gl-form-group + v-if="form.denylistType === $options.DENYLIST_TYPE_FILE" + :description="$options.i18n.domainDenyListFileDescription" + :label="$options.i18n.domainDenyListFileLabel" + label-for="domain-denylist-file-input" + data-testid="domain-denylist-file-input-group" + > + <input + id="domain-denylist-file-input" + class="form-control gl-form-input" + type="file" + accept=".txt,.conf" + name="application_setting[domain_denylist_file]" + /> + </gl-form-group> + + <gl-form-group + v-if="form.denylistType !== $options.DENYLIST_TYPE_FILE" + :description="$options.i18n.domainDenyListListDescription" + :label="$options.i18n.domainDenyListListLabel" + data-testid="domain-denylist-raw-input-group" + > + <textarea + v-model="form.domainDenylistRaw" + :placeholder="$options.i18n.domainPlaceholder" + rows="8" + class="form-control gl-form-input" + name="application_setting[domain_denylist_raw]" + ></textarea> + </gl-form-group> + + <gl-form-group :label="$options.i18n.emailRestrictionsEnabledGroupLabel"> + <signup-checkbox + v-model="form.emailRestrictionsEnabled" + name="application_setting[email_restrictions_enabled]" + :label="$options.i18n.emailRestrictionsEnabledLabel" + /> + </gl-form-group> + + <gl-form-group :label="$options.i18n.emailRestrictionsGroupLabel"> + <textarea + v-model="form.emailRestrictions" + rows="4" + class="form-control gl-form-input" + name="application_setting[email_restrictions]" + ></textarea> + + <gl-sprintf + :message=" + s__( + 'ApplicationSettings|Restricts sign-ups for email addresses that match the given regex. See the %{linkStart}supported syntax%{linkEnd} for more information.', + ) + " + > + <template #link="{ content }"> + <gl-link :href="form.supportedSyntaxLinkUrl" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </gl-form-group> + + <gl-form-group + :label="$options.i18n.afterSignUpTextGroupLabel" + :description="$options.i18n.afterSignUpTextGroupDescription" + > + <textarea + v-model="form.afterSignUpText" + rows="4" + class="form-control gl-form-input" + name="application_setting[after_sign_up_text]" + ></textarea> + </gl-form-group> + </section> + <gl-button + data-qa-selector="save_changes_button" + variant="confirm" + @click="submitButtonHandler" + > + {{ $options.i18n.buttonText }} + </gl-button> + </form> +</template> diff --git a/app/assets/javascripts/pages/admin/application_settings/general/index.js b/app/assets/javascripts/pages/admin/application_settings/general/index.js index eda1a9d3599..c48d99da990 100644 --- a/app/assets/javascripts/pages/admin/application_settings/general/index.js +++ b/app/assets/javascripts/pages/admin/application_settings/general/index.js @@ -1,27 +1,9 @@ -import Vue from 'vue'; -import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue'; import initUserInternalRegexPlaceholder from '../account_and_limits'; +import initGitpod from '../gitpod'; +import initSignupRestrictions from '../signup_restrictions'; (() => { initUserInternalRegexPlaceholder(); - - const el = document.querySelector('#js-gitpod-settings-help-text'); - if (!el) { - return; - } - - const { message, messageUrl } = el.dataset; - - // eslint-disable-next-line no-new - new Vue({ - el, - render(createElement) { - return createElement(IntegrationHelpText, { - props: { - message, - messageUrl, - }, - }); - }, - }); + initGitpod(); + initSignupRestrictions(); })(); diff --git a/app/assets/javascripts/pages/admin/application_settings/gitpod.js b/app/assets/javascripts/pages/admin/application_settings/gitpod.js new file mode 100644 index 00000000000..74e46617d52 --- /dev/null +++ b/app/assets/javascripts/pages/admin/application_settings/gitpod.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue'; + +export default function initGitpod() { + const el = document.querySelector('#js-gitpod-settings-help-text'); + + if (!el) { + return false; + } + + const { message, messageUrl } = el.dataset; + + return new Vue({ + el, + render(createElement) { + return createElement(IntegrationHelpText, { + props: { + message, + messageUrl, + }, + }); + }, + }); +} diff --git a/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js new file mode 100644 index 00000000000..70b896f6372 --- /dev/null +++ b/app/assets/javascripts/pages/admin/application_settings/signup_restrictions.js @@ -0,0 +1,31 @@ +import Vue from 'vue'; +import SignupForm from './general/components/signup_form.vue'; +import { getParsedDataset } from './utils'; + +export default function initSignupRestrictions(elementSelector = '#js-signup-form') { + const el = document.querySelector(elementSelector); + + if (!el) { + return false; + } + + const parsedDataset = getParsedDataset({ + dataset: el.dataset, + booleanAttributes: [ + 'signupEnabled', + 'requireAdminApprovalAfterUserSignup', + 'sendUserConfirmationEmail', + 'domainDenylistEnabled', + 'denylistTypeRawSelected', + 'emailRestrictionsEnabled', + ], + }); + + return new Vue({ + el, + provide: { + ...parsedDataset, + }, + render: (createElement) => createElement(SignupForm), + }); +} diff --git a/app/assets/javascripts/pages/admin/application_settings/utils.js b/app/assets/javascripts/pages/admin/application_settings/utils.js new file mode 100644 index 00000000000..5462a13d523 --- /dev/null +++ b/app/assets/javascripts/pages/admin/application_settings/utils.js @@ -0,0 +1,21 @@ +import { includes } from 'lodash'; +import { parseBoolean } from '~/lib/utils/common_utils'; + +/** + * Returns a new dataset that has all the values of keys indicated in + * booleanAttributes transformed by the parseBoolean() helper function + * + * @param {Object} + * @returns {Object} + */ +export const getParsedDataset = ({ dataset = {}, booleanAttributes = [] } = {}) => { + const parsedDataset = {}; + + Object.keys(dataset).forEach((key) => { + parsedDataset[key] = includes(booleanAttributes, key) + ? parseBoolean(dataset[key]) + : dataset[key]; + }); + + return parsedDataset; +}; diff --git a/app/assets/javascripts/pages/projects/packages/infrastructure_registry/index/index.js b/app/assets/javascripts/pages/projects/packages/infrastructure_registry/index/index.js index c94782fdf1b..dfb750eca41 100644 --- a/app/assets/javascripts/pages/projects/packages/infrastructure_registry/index/index.js +++ b/app/assets/javascripts/pages/projects/packages/infrastructure_registry/index/index.js @@ -1,3 +1,3 @@ -import initPackageList from '~/packages/list/packages_list_app_bundle'; +import initList from '~/packages_and_registries/infrastructure_registry/list_app_bundle'; -initPackageList(); +initList(); diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index f684cb090e2..d01c9a9fd42 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -11,17 +11,3 @@ background: $blue-400; } } - -.runner-status { - &.runner-status-online { - background-color: $green-600; - } - - &.runner-status-offline { - background-color: $gray-darkest; - } - - &.runner-status-paused { - background-color: $red-500; - } -} diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index ba5d4e8c65a..82347053d6f 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -4,18 +4,33 @@ module Ci module RunnersHelper include IconsHelper - def runner_status_icon(runner) + def runner_status_icon(runner, size: 16, icon_class: '') status = runner.status + + title = '' + icon = 'warning-solid' + span_class = '' + case status when :not_connected - content_tag(:span, title: _("New runner. Has not connected yet")) do - sprite_icon("warning-solid", size: 24, css_class: "gl-vertical-align-bottom!") - end + title = s_("Runners|New runner, has not connected yet") + icon = 'warning-solid' + when :online + title = s_("Runners|Runner is online, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } + icon = 'status-active' + span_class = 'gl-text-green-500' + when :offline + title = s_("Runners|Runner is offline, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } + icon = 'status-failed' + span_class = 'gl-text-red-500' + when :paused + title = s_("Runners|Runner is paused, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) } + icon = 'status-paused' + span_class = 'gl-text-gray-600' + end - when :online, :offline, :paused - content_tag :span, nil, - class: "gl-display-inline-block gl-avatar gl-avatar-s16 gl-avatar-circle runner-status runner-status-#{status}", - title: _("Runner is %{status}, last contact was %{runner_contact} ago") % { status: status, runner_contact: time_ago_in_words(runner.contacted_at) } + content_tag(:span, class: span_class, title: title, data: { toggle: 'tooltip', container: 'body', testid: 'runner_status_icon', qa_selector: "runner_status_#{status}_content" }) do + sprite_icon(icon, size: size, css_class: icon_class) end end diff --git a/app/models/namespaces/traversal/recursive.rb b/app/models/namespaces/traversal/recursive.rb index aac70a1a0a9..409438f53d2 100644 --- a/app/models/namespaces/traversal/recursive.rb +++ b/app/models/namespaces/traversal/recursive.rb @@ -22,6 +22,7 @@ module Namespaces object_hierarchy(self.class.where(id: id)) .all_objects end + alias_method :recursive_self_and_hierarchy, :self_and_hierarchy # Returns all the ancestors of the current namespaces. def ancestors @@ -30,6 +31,7 @@ module Namespaces object_hierarchy(self.class.where(id: parent_id)) .base_and_ancestors end + alias_method :recursive_ancestors, :ancestors # returns all ancestors upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned @@ -44,17 +46,20 @@ module Namespaces object_hierarchy(self.class.where(id: id)) .base_and_ancestors(hierarchy_order: hierarchy_order) end + alias_method :recursive_self_and_ancestors, :self_and_ancestors # Returns all the descendants of the current namespace. def descendants object_hierarchy(self.class.where(parent_id: id)) .base_and_descendants end + alias_method :recursive_descendants, :descendants def self_and_descendants object_hierarchy(self.class.where(id: id)) .base_and_descendants end + alias_method :recursive_self_and_descendants, :self_and_descendants def object_hierarchy(ancestors_base) Gitlab::ObjectHierarchy.new(ancestors_base, options: { use_distinct: Feature.enabled?(:use_distinct_in_object_hierarchy, self) }) diff --git a/app/views/admin/application_settings/_signup.html.haml b/app/views/admin/application_settings/_signup.html.haml index 272eba67b1b..a5b47159239 100644 --- a/app/views/admin/application_settings/_signup.html.haml +++ b/app/views/admin/application_settings/_signup.html.haml @@ -1,80 +1,20 @@ -= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-signup-settings'), html: { class: 'fieldset-form' } do |f| - = form_errors(@application_setting) += form_errors(@application_setting) - %fieldset - .form-group - .form-check - = f.check_box :signup_enabled, class: 'form-check-input', data: { qa_selector: 'signup_enabled_checkbox' } - = f.label :signup_enabled, class: 'form-check-label' do - Sign-up enabled - .form-text.text-muted - = _("When enabled, any user visiting %{host} will be able to create an account.") % { host: "#{new_user_session_url(host: Gitlab.config.gitlab.host)}" } - .form-group - .form-check - = f.check_box :require_admin_approval_after_user_signup, class: 'form-check-input', data: { qa_selector: 'require_admin_approval_after_user_signup_checkbox' } - = f.label :require_admin_approval_after_user_signup, class: 'form-check-label' do - = _('Require admin approval for new sign-ups') - .form-text.text-muted - = _("When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled.") % { host: "#{new_user_session_url(host: Gitlab.config.gitlab.host)}" } - .form-group - .form-check - = f.check_box :send_user_confirmation_email, class: 'form-check-input' - = f.label :send_user_confirmation_email, class: 'form-check-label' do - Send confirmation email on sign-up - - = render_if_exists 'admin/application_settings/new_user_signups_cap', form: f - - .form-group - = f.label :minimum_password_length, _('Minimum password length (number of characters)'), class: 'label-bold' - = f.number_field :minimum_password_length, class: 'form-control gl-form-input', rows: 4, min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH, max: Devise.password_length.max - - password_policy_guidelines_link = link_to _('Password Policy Guidelines'), 'https://about.gitlab.com/handbook/security/#gitlab-password-policy-guidelines', target: '_blank', rel: 'noopener noreferrer nofollow' - .form-text.text-muted - = _("See GitLab's %{password_policy_guidelines}").html_safe % { password_policy_guidelines: password_policy_guidelines_link } - .form-group - = f.label :domain_allowlist, _('Allowed domains for sign-ups'), class: 'label-bold' - = f.text_area :domain_allowlist_raw, placeholder: 'domain.com', class: 'form-control gl-form-input', rows: 8 - .form-text.text-muted ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com - .form-group - = f.label :domain_denylist_enabled, _('Domain denylist'), class: 'label-bold' - .form-check - = f.check_box :domain_denylist_enabled, class: 'form-check-input' - = f.label :domain_denylist_enabled, class: 'form-check-label' do - Enable domain denylist for sign ups - .form-group - .form-check - = radio_button_tag :denylist_type, :file, false, class: 'form-check-input' - = label_tag :denylist_type_file, class: 'form-check-label' do - .option-title - Upload denylist file - .form-check - = radio_button_tag :denylist_type, :raw, @application_setting.domain_denylist.present? || @application_setting.domain_denylist.blank?, class: 'form-check-input' - = label_tag :denylist_type_raw, class: 'form-check-label' do - .option-title - Enter denylist manually - .form-group.js-denylist-file - = f.label :domain_denylist_file, _('Denylist file'), class: 'label-bold' - = f.file_field :domain_denylist_file, class: 'form-control gl-form-input', accept: '.txt,.conf' - .form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries. - .form-group.js-denylist-raw - = f.label :domain_denylist, _('Denied domains for sign-ups'), class: 'label-bold' - = f.text_area :domain_denylist_raw, placeholder: 'domain.com', class: 'form-control gl-form-input', rows: 8 - .form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com - .form-group - = f.label :email_restrictions_enabled, _('Email restrictions'), class: 'label-bold' - .form-check - = f.check_box :email_restrictions_enabled, class: 'form-check-input' - = f.label :email_restrictions_enabled, class: 'form-check-label' do - = _('Enable email restrictions for sign ups') - .form-group - = f.label :email_restrictions, _('Email restrictions for sign-ups'), class: 'label-bold' - = f.text_area :email_restrictions, class: 'form-control gl-form-input', rows: 4 - .form-text.text-muted - - supported_syntax_link_url = 'https://github.com/google/re2/wiki/Syntax' - - supported_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: supported_syntax_link_url } - = _('Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information.').html_safe % { supported_syntax_link_start: supported_syntax_link_start, supported_syntax_link_end: '</a>'.html_safe } - - .form-group - = f.label :after_sign_up_text, class: 'label-bold' - = f.text_area :after_sign_up_text, class: 'form-control gl-form-input', rows: 4 - .form-text.text-muted Markdown enabled - = f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' } +#js-signup-form{ data: { host: new_user_session_url(host: Gitlab.config.gitlab.host), + settings_path: general_admin_application_settings_path(anchor: 'js-signup-settings'), + signup_enabled: @application_setting[:signup_enabled].to_s, + require_admin_approval_after_user_signup: @application_setting[:require_admin_approval_after_user_signup].to_s, + send_user_confirmation_email: @application_setting[:send_user_confirmation_email].to_s, + minimum_password_length: @application_setting[:minimum_password_length], + minimum_password_length_min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH, + minimum_password_length_max: Devise.password_length.max, + minimum_password_length_help_link: 'https://about.gitlab.com/handbook/security/#gitlab-password-policy-guidelines', + domain_allowlist_raw: @application_setting.domain_allowlist_raw, + new_user_signups_cap: @application_setting[:new_user_signups_cap].to_s, + domain_denylist_enabled: @application_setting[:domain_denylist_enabled].to_s, + denylist_type_raw_selected: (@application_setting.domain_denylist.present? || @application_setting.domain_denylist.blank?).to_s, + domain_denylist_raw: @application_setting.domain_denylist_raw, + email_restrictions_enabled: @application_setting[:email_restrictions_enabled].to_s, + supported_syntax_link_url: 'https://github.com/google/re2/wiki/Syntax', + email_restrictions: @application_setting.email_restrictions, + after_sign_up_text: @application_setting[:after_sign_up_text] } } diff --git a/app/views/projects/packages/infrastructure_registry/index.html.haml b/app/views/projects/packages/infrastructure_registry/index.html.haml index e8be9051275..5a118997ff9 100644 --- a/app/views/projects/packages/infrastructure_registry/index.html.haml +++ b/app/views/projects/packages/infrastructure_registry/index.html.haml @@ -5,6 +5,6 @@ .col-12 #js-vue-packages-list{ data: { resource_id: @project.id, page_type: 'project', - empty_list_help_url: help_page_path('user/packages/package_registry/index'), - empty_list_illustration: image_path('illustrations/no-packages.svg'), - package_help_url: help_page_path('user/packages/index') } } + empty_list_help_url: help_page_path('user/infrastructure/index'), + empty_list_illustration: image_path('illustrations/empty-state/empty-terraform-register-lg.svg'), + package_help_url: help_page_path('user/infrastructure/index') } } diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 0d67deb34cf..bc043a5f394 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -1,6 +1,6 @@ %li.runner{ id: dom_id(runner) } %h4.gl-font-weight-normal - = runner_status_icon(runner) + = runner_status_icon(runner, size: 16, icon_class: "gl-vertical-align-middle!") - if @project_runners.include?(runner) = link_to _("%{token}...") % { token: runner.short_sha }, project_runner_path(@project, runner), class: 'commit-sha has-tooltip', title: _("Partial token for reference only") diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 2f2fd19a3aa..ef7b5eb026e 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2377,7 +2377,7 @@ :idempotent: :tags: [] - :name: update_highest_role - :feature_category: :authentication_and_authorization + :feature_category: :utilization :has_external_dependencies: :urgency: :high :resource_boundary: :unknown diff --git a/app/workers/update_highest_role_worker.rb b/app/workers/update_highest_role_worker.rb index 1e2c974b6e5..952f1e511ea 100644 --- a/app/workers/update_highest_role_worker.rb +++ b/app/workers/update_highest_role_worker.rb @@ -3,7 +3,7 @@ class UpdateHighestRoleWorker include ApplicationWorker - feature_category :authentication_and_authorization + feature_category :utilization urgency :high weight 2 |