diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-15 12:10:30 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-15 12:10:30 +0300 |
commit | cd5f4619db234c00d0c01ed63bb92df6c10b0fd3 (patch) | |
tree | 09ef12a2231c616d4f747b9d5b83597ae179bb06 /app | |
parent | 024f77efd68833bb78540ff9b4c7b4ec4b9dfe39 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
22 files changed, 492 insertions, 375 deletions
diff --git a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue index 57d5d46ceb4..92478e10289 100644 --- a/app/assets/javascripts/admin/abuse_report/components/report_actions.vue +++ b/app/assets/javascripts/admin/abuse_report/components/report_actions.vue @@ -95,10 +95,12 @@ export default { return; } - axios - .put(this.report.updatePath, this.form) - .then(this.handleResponse) - .catch(this.handleError); + // TODO: In 16.4 use moderateUserPath without falling back to using updatePath + // See https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/167?work_item_iid=443 + const { moderateUserPath, updatePath } = this.report; + const path = moderateUserPath || updatePath; + + axios.put(path, this.form).then(this.handleResponse).catch(this.handleError); }, handleResponse({ data }) { this.toggleActionsDrawer(); diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index 4860215d8f2..ec17bbea48f 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -1,5 +1,5 @@ <script> -import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; +import { GlButton, GlIcon, GlLoadingIcon } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { s__ } from '~/locale'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; @@ -14,8 +14,9 @@ export default { ConfirmModal, KeysPanel, NavigationTabs, - GlLoadingIcon, + GlButton, GlIcon, + GlLoadingIcon, }, props: { endpoint: { @@ -42,6 +43,10 @@ export default { available_project_keys: s__('DeployKeys|Privately accessible deploy keys'), public_keys: s__('DeployKeys|Publicly accessible deploy keys'), }, + i18n: { + loading: s__('DeployKeys|Loading deploy keys'), + addButton: s__('DeployKeys|Add new key'), + }, computed: { tabs() { return Object.keys(this.$options.scopes).map((scope) => { @@ -132,23 +137,41 @@ export default { </script> <template> - <div class="gl-mb-3 deploy-keys"> + <div class="deploy-keys"> <confirm-modal :visible="confirmModalVisible" @remove="removeKey" @cancel="cancel" /> <gl-loading-icon v-if="isLoading && !hasKeys" - :label="s__('DeployKeys|Loading deploy keys')" - size="lg" + :label="$options.i18n.loading" + size="sm" + class="gl-m-5" /> <template v-else-if="hasKeys"> - <div class="top-area scrolling-tabs-container inner-page-scroll-tabs"> - <div class="fade-left"> - <gl-icon name="chevron-lg-left" :size="12" /> - </div> - <div class="fade-right"> - <gl-icon name="chevron-lg-right" :size="12" /> + <div class="gl-new-card-header gl-align-items-center gl-pt-0 gl-pb-0 gl-pl-0"> + <div class="top-area scrolling-tabs-container inner-page-scroll-tabs gl-border-b-0"> + <div class="fade-left"> + <gl-icon name="chevron-lg-left" :size="12" /> + </div> + <div class="fade-right"> + <gl-icon name="chevron-lg-right" :size="12" /> + </div> + + <navigation-tabs + :tabs="tabs" + scope="deployKeys" + class="gl-rounded-lg" + @onChangeTab="onChangeTab" + /> </div> - <navigation-tabs :tabs="tabs" scope="deployKeys" @onChangeTab="onChangeTab" /> + <div class="gl-new-card-actions"> + <gl-button + size="small" + class="js-toggle-button js-toggle-content" + data-testid="add-new-deploy-key-button" + > + {{ $options.i18n.addButton }} + </gl-button> + </div> </div> <keys-panel :project-id="projectId" diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index 79c45553659..16c745d8cff 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -1,6 +1,6 @@ <!-- eslint-disable vue/multi-word-component-names --> <script> -import { GlIcon, GlLink, GlTooltipDirective, GlButton } from '@gitlab/ui'; +import { GlBadge, GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { head, tail } from 'lodash'; import { s__, sprintf } from '~/locale'; import timeagoMixin from '~/vue_shared/mixins/timeago'; @@ -10,9 +10,9 @@ import ActionBtn from './action_btn.vue'; export default { components: { ActionBtn, + GlBadge, GlButton, GlIcon, - GlLink, }, directives: { GlTooltip: GlTooltipDirective, @@ -111,12 +111,21 @@ export default { </script> <template> - <div class="gl-responsive-table-row deploy-key"> + <div + class="gl-responsive-table-row gl-align-items-flex-start deploy-key gl-bg-gray-10 gl-md-pl-5 gl-md-pr-5 gl-border-gray-100!" + > <div class="table-section section-40"> - <div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Deploy key') }}</div> + <div + role="rowheader" + class="table-mobile-header gl-align-self-start gl-font-weight-bold gl-text-gray-700" + > + {{ s__('DeployKeys|Deploy key') }} + </div> <div class="table-mobile-content" data-testid="key-container"> - <strong class="title" data-testid="key-title-content"> {{ deployKey.title }} </strong> - <dl> + <p class="title gl-font-weight-semibold gl-text-gray-700" data-testid="key-title-content"> + {{ deployKey.title }} + </p> + <dl class="gl-font-sm gl-mb-0"> <dt>{{ __('SHA256') }}</dt> <dd class="fingerprint" data-testid="key-sha256-fingerprint-content"> {{ deployKey.fingerprint_sha256 }} @@ -133,53 +142,62 @@ export default { </div> </div> <div class="table-section section-20 section-wrap"> - <div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Project usage') }}</div> - <div class="table-mobile-content deploy-project-list"> + <div role="rowheader" class="table-mobile-header gl-font-weight-bold gl-text-gray-700"> + {{ s__('DeployKeys|Project usage') }} + </div> + <div class="table-mobile-content deploy-project-list gl-display-flex gl-flex-wrap"> <template v-if="projects.length > 0"> - <gl-link + <gl-badge v-gl-tooltip :title="projectTooltipTitle(firstProject)" - class="label deploy-project-label" + :icon="firstProject.can_push ? 'lock-open' : 'lock'" + class="deploy-project-label gl-mr-2 gl-mb-2 gl-truncate" > - <span> {{ firstProject.project.full_name }} </span> - <gl-icon :name="firstProject.can_push ? 'lock-open' : 'lock'" /> - </gl-link> - <gl-link + <span class="gl-text-truncate">{{ firstProject.project.full_name }}</span> + </gl-badge> + + <gl-badge v-if="isExpandable" v-gl-tooltip :title="restProjectsTooltip" - class="label deploy-project-label" - @click="toggleExpanded" + class="deploy-project-label gl-mr-2 gl-mb-2 gl-truncate" + href="#" + @click.native="toggleExpanded" > - <span>{{ restProjectsLabel }}</span> - </gl-link> - <gl-link + <span class="gl-text-truncate">{{ restProjectsLabel }}</span> + </gl-badge> + + <gl-badge v-for="deployKeysProject in restProjects" v-else-if="isExpanded" :key="deployKeysProject.project.full_path" v-gl-tooltip :href="deployKeysProject.project.full_path" :title="projectTooltipTitle(deployKeysProject)" - class="label deploy-project-label" + :icon="deployKeysProject.can_push ? 'lock-open' : 'lock'" + class="deploy-project-label gl-mr-2 gl-mb-2 gl-truncate" > - <span> {{ deployKeysProject.project.full_name }} </span> - <gl-icon :name="deployKeysProject.can_push ? 'lock-open' : 'lock'" /> - </gl-link> + <span class="gl-text-truncate">{{ deployKeysProject.project.full_name }}</span> + </gl-badge> </template> - <span v-else class="text-secondary">{{ __('None') }}</span> + <span v-else class="gl-text-secondary">{{ __('None') }}</span> </div> </div> <div class="table-section section-15"> - <div role="rowheader" class="table-mobile-header">{{ __('Created') }}</div> - <div class="table-mobile-content text-secondary key-created-at"> + <div role="rowheader" class="table-mobile-header gl-font-weight-bold gl-text-gray-700"> + {{ __('Created') }} + </div> + <div class="table-mobile-content gl-text-gray-700 key-created-at"> <span v-gl-tooltip :title="tooltipTitle(deployKey.created_at)"> <gl-icon name="calendar" /> <span>{{ timeFormatted(deployKey.created_at) }}</span> </span> </div> </div> <div class="table-section section-15"> - <div role="rowheader" class="table-mobile-header">{{ __('Expires') }}</div> - <div class="table-mobile-content text-secondary key-expires-at"> + <div role="rowheader" class="table-mobile-header gl-font-weight-bold gl-text-gray-700"> + {{ __('Expires') }} + </div> + <div class="table-mobile-content gl-text-gray-700 key-expires-at"> <span v-if="deployKey.expires_at" v-gl-tooltip @@ -214,7 +232,7 @@ export default { :deploy-key="deployKey" :title="__('Remove')" :aria-label="__('Remove')" - category="primary" + category="secondary" variant="danger" icon="remove" type="remove" @@ -229,7 +247,7 @@ export default { type="disable" data-container="body" icon="cancel" - category="primary" + category="secondary" variant="danger" /> </div> diff --git a/app/assets/javascripts/deploy_keys/components/keys_panel.vue b/app/assets/javascripts/deploy_keys/components/keys_panel.vue index e04cbbe72b9..dac63188aa5 100644 --- a/app/assets/javascripts/deploy_keys/components/keys_panel.vue +++ b/app/assets/javascripts/deploy_keys/components/keys_panel.vue @@ -28,9 +28,12 @@ export default { </script> <template> - <div class="deploy-keys-panel table-holder"> + <div class="deploy-keys-panel table-holder gl-bg-white gl-rounded-lg"> <template v-if="keys.length > 0"> - <div role="row" class="gl-responsive-table-row table-row-header"> + <div + role="row" + class="gl-responsive-table-row table-row-header gl-font-base gl-font-weight-bold gl-text-gray-900 gl-md-pl-5 gl-md-pr-5 gl-bg-gray-10 gl-border-gray-100!" + > <div role="rowheader" class="table-section section-40"> {{ s__('DeployKeys|Deploy key') }} </div> @@ -50,8 +53,8 @@ export default { :project-id="projectId" /> </template> - <div v-else class="settings-message text-center gl-mt-5"> - {{ s__('DeployKeys|No deploy keys found. Create one with the form above.') }} + <div v-else class="gl-new-card-empty gl-bg-gray-10 gl-text-center gl-p-5"> + {{ s__('DeployKeys|No deploy keys found, start by adding a new one above.') }} </div> </div> </template> diff --git a/app/assets/javascripts/nav/components/new_nav_toggle.vue b/app/assets/javascripts/nav/components/new_nav_toggle.vue index 82302ec4602..4e5d6b0ce6c 100644 --- a/app/assets/javascripts/nav/components/new_nav_toggle.vue +++ b/app/assets/javascripts/nav/components/new_nav_toggle.vue @@ -68,10 +68,15 @@ export default { <gl-disclosure-dropdown-item v-if="newNavigation" @action="toggleNav"> <div class="gl-new-dropdown-item-content"> <div - class="gl-new-dropdown-item-text-wrapper gl-display-flex! gl-justify-content-space-between gl-align-items-center gl-py-2!" + class="gl-new-dropdown-item-text-wrapper gl-display-flex! gl-justify-content-space-between gl-align-items-center gl-py-2! gl-gap-3" > {{ $options.i18n.toggleMenuItemLabel }} - <gl-toggle :value="isEnabled" :label="$options.i18n.toggleLabel" label-position="hidden" /> + <gl-toggle + class="gl-flex-grow-0!" + :value="isEnabled" + :label="$options.i18n.toggleLabel" + label-position="hidden" + /> </div> </div> </gl-disclosure-dropdown-item> @@ -84,11 +89,12 @@ export default { </div> <div - class="menu-item gl-cursor-pointer gl-display-flex! gl-justify-content-space-between gl-align-items-center" + class="menu-item gl-cursor-pointer gl-display-flex! gl-justify-content-space-between gl-align-items-center gl-gap-3" @click.prevent.stop="toggleNav" > {{ $options.i18n.toggleMenuItemLabel }} <gl-toggle + class="gl-flex-grow-0!" :value="isEnabled" :label="$options.i18n.toggleLabel" label-position="hidden" diff --git a/app/assets/javascripts/service_desk/components/empty_state_without_any_issues.vue b/app/assets/javascripts/service_desk/components/empty_state_without_any_issues.vue index 0679d31a8b8..9dbed2c2579 100644 --- a/app/assets/javascripts/service_desk/components/empty_state_without_any_issues.vue +++ b/app/assets/javascripts/service_desk/components/empty_state_without_any_issues.vue @@ -1,6 +1,5 @@ <script> import { GlEmptyState, GlLink } from '@gitlab/ui'; -import { helpPagePath } from '~/helpers/help_page_helper'; import { noIssuesSignedOutButtonText, infoBannerTitle, @@ -17,7 +16,6 @@ export default { infoBannerAdminNote, learnMore, }, - serviceDeskHelpPagePath: helpPagePath('user/project/service_desk/index'), components: { GlEmptyState, GlLink, @@ -29,6 +27,7 @@ export default { 'canAdminIssues', 'isServiceDeskEnabled', 'serviceDeskEmailAddress', + 'serviceDeskHelpPath', ], computed: { canSeeEmailAddress() { @@ -50,7 +49,7 @@ export default { {{ $options.i18n.infoBannerAdminNote }} <br /><code>{{ serviceDeskEmailAddress }}</code> </p> <p>{{ $options.i18n.infoBannerUserNote }}</p> - <gl-link :href="$options.serviceDeskHelpPagePath" target="_blank"> + <gl-link :href="serviceDeskHelpPath"> {{ $options.i18n.learnMore }} </gl-link> </template> @@ -67,7 +66,7 @@ export default { > <template #description> <p>{{ $options.i18n.infoBannerUserNote }}</p> - <gl-link :href="$options.serviceDeskHelpPagePath"> + <gl-link :href="serviceDeskHelpPath"> {{ $options.i18n.learnMore }} </gl-link> </template> diff --git a/app/assets/javascripts/service_desk/components/info_banner.vue b/app/assets/javascripts/service_desk/components/info_banner.vue index 8aaced839a5..5667ee2f31d 100644 --- a/app/assets/javascripts/service_desk/components/info_banner.vue +++ b/app/assets/javascripts/service_desk/components/info_banner.vue @@ -51,7 +51,7 @@ export default { </p> <p> {{ $options.i18n.infoBannerUserNote }} - <gl-link :href="serviceDeskHelpPath" target="_blank">{{ $options.i18n.learnMore }}</gl-link + <gl-link :href="serviceDeskHelpPath">{{ $options.i18n.learnMore }}</gl-link >. </p> <p v-if="canEnableServiceDesk" class="gl-mt-3"> diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 71936f37333..b9cae28537d 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -65,7 +65,6 @@ white-space: normal; } - .deploy-project-label, .key-created-at { svg { vertical-align: text-top; @@ -83,18 +82,6 @@ .deploy-project-list { margin-bottom: -$gl-padding-4; - - a.deploy-project-label { - margin-right: $gl-padding-4; - margin-bottom: $gl-padding-4; - color: $gl-text-color-secondary; - background-color: $gray-50; - line-height: $gl-btn-line-height; - - &:hover { - color: $blue-600; - } - } } .vs-public { diff --git a/app/controllers/admin/abuse_reports_controller.rb b/app/controllers/admin/abuse_reports_controller.rb index 6b998c3d494..329c4e4921a 100644 --- a/app/controllers/admin/abuse_reports_controller.rb +++ b/app/controllers/admin/abuse_reports_controller.rb @@ -4,7 +4,7 @@ class Admin::AbuseReportsController < Admin::ApplicationController feature_category :insider_threat before_action :set_status_param, only: :index, if: -> { Feature.enabled?(:abuse_reports_list) } - before_action :find_abuse_report, only: [:show, :update, :destroy] + before_action :find_abuse_report, only: [:show, :moderate_user, :update, :destroy] def index @abuse_reports = AbuseReportsFinder.new(params).execute @@ -12,8 +12,22 @@ class Admin::AbuseReportsController < Admin::ApplicationController def show; end + # Kept for backwards compatibility. + # TODO: See https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/167?work_item_iid=443 + # In 16.4 remove or re-use this endpoint after frontend has migrated to using moderate_user endpoint def update - response = Admin::AbuseReportUpdateService.new(@abuse_report, current_user, permitted_params).execute + response = Admin::AbuseReports::ModerateUserService.new(@abuse_report, current_user, permitted_params).execute + + if response.success? + render json: { message: response.message } + else + render json: { message: response.message }, status: :unprocessable_entity + end + end + + def moderate_user + response = Admin::AbuseReports::ModerateUserService.new(@abuse_report, current_user, permitted_params).execute + if response.success? render json: { message: response.message } else diff --git a/app/models/concerns/enum_inheritance.rb b/app/models/concerns/enum_inheritance.rb new file mode 100644 index 00000000000..1df1f3d43fd --- /dev/null +++ b/app/models/concerns/enum_inheritance.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module EnumInheritance + # == STI through Enum + # + # WARNING: Usage of STI is heavily discouraged: https://docs.gitlab.com/ee/development/database/single_table_inheritance.html + # + # Active Record allows definition of STI through the <tt>Base.inheritance_column</tt>. However, this stores the class + # name as string into the record, which is heavy and unnecessary. EnumInheritance adapts ActiveRecord to use an enum + # instead. + # + # Details: + # - Correct class mapping is specified in the <tt>self.sti_type_map<\tt>, which maps the symbol of the type to + # a fully classified class as string. + # - If the type passed does not have an specified class, then the class will be the base class + # + # Example + # class Animal + # include EnumInheritable + # + # enum animal_type: { + # dog: 1, + # cat: 2, + # bird: 3 + # } + # + # def self.inheritance_column_to_class_map = { + # dog: 'Animals::Dog', + # cat: 'Animals::Cat' + # } + # + # def self.inheritance_column = 'animal_type' + # end + # + # class Animals::Dog < Animal; end + # class Animals::Cat < Animal; end + extend ActiveSupport::Concern + + included do + def self.sti_class_to_enum_map = inheritance_column_to_class_map.invert + end + + class_methods do + extend ::Gitlab::Utils::Override + + def inheritance_column_to_class_map = {}.freeze + + override :sti_class_for + def sti_class_for(type_name) + inheritance_column_to_class_map[type_name.to_sym]&.constantize || base_class + end + + override :sti_name + def sti_name + sti_class_to_enum_map[name].to_s + end + end +end diff --git a/app/serializers/admin/abuse_report_details_entity.rb b/app/serializers/admin/abuse_report_details_entity.rb index f0e84fc44d2..3efb8508e5e 100644 --- a/app/serializers/admin/abuse_report_details_entity.rb +++ b/app/serializers/admin/abuse_report_details_entity.rb @@ -79,9 +79,17 @@ module Admin expose :reported_content, as: :content expose :reported_from_url, as: :url expose :screenshot_path, as: :screenshot + + # Kept for backwards compatibility. + # TODO: See https://gitlab.com/gitlab-org/modelops/anti-abuse/team-tasks/-/issues/167?work_item_iid=443 + # In 16.4 remove or re-use this field after frontend has migrated to using moderate_user_path expose :update_path do |report| admin_abuse_report_path(report) end + + expose :moderate_user_path do |report| + moderate_user_admin_abuse_report_path(report) + end end end end diff --git a/app/services/admin/abuse_report_update_service.rb b/app/services/admin/abuse_report_update_service.rb deleted file mode 100644 index 12cf8bf14a8..00000000000 --- a/app/services/admin/abuse_report_update_service.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -module Admin - class AbuseReportUpdateService < BaseService - attr_reader :abuse_report, :params, :current_user, :action - - def initialize(abuse_report, current_user, params) - @abuse_report = abuse_report - @current_user = current_user - @params = params - @action = determine_action - end - - def execute - return ServiceResponse.error(message: 'Admin is required') unless current_user&.can_admin_all_resources? - return ServiceResponse.error(message: 'Action is required') unless action.present? - - result = perform_action - if result[:status] == :success - event = close_report_and_record_event - ServiceResponse.success(message: event.success_message) - else - ServiceResponse.error(message: result[:message]) - end - end - - private - - def determine_action - action = params[:user_action] - if action.in?(ResourceEvents::AbuseReportEvent.actions.keys) - action.to_sym - elsif close_report? - :close_report - end - end - - def perform_action - case action - when :ban_user then ban_user - when :block_user then block_user - when :delete_user then delete_user - when :close_report then close_report - end - end - - def ban_user - Users::BanService.new(current_user).execute(abuse_report.user) - end - - def block_user - Users::BlockService.new(current_user).execute(abuse_report.user) - end - - def delete_user - abuse_report.user.delete_async(deleted_by: current_user) - success - end - - def close_report - return error('Report already closed') if abuse_report.closed? - - abuse_report.closed! - success - end - - def close_report_and_record_event - event = action - - if close_report? && action != :close_report - close_report - event = "#{action}_and_close_report" - end - - record_event(event) - end - - def close_report? - params[:close].to_s == 'true' - end - - def record_event(action) - reason = params[:reason] - unless reason.in?(ResourceEvents::AbuseReportEvent.reasons.keys) - reason = ResourceEvents::AbuseReportEvent.reasons[:other] - end - - abuse_report.events.create(action: action, user: current_user, reason: reason, comment: params[:comment]) - end - end -end diff --git a/app/services/admin/abuse_reports/moderate_user_service.rb b/app/services/admin/abuse_reports/moderate_user_service.rb new file mode 100644 index 00000000000..da61a4dc8f6 --- /dev/null +++ b/app/services/admin/abuse_reports/moderate_user_service.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module Admin + module AbuseReports + class ModerateUserService < BaseService + attr_reader :abuse_report, :params, :current_user, :action + + def initialize(abuse_report, current_user, params) + @abuse_report = abuse_report + @current_user = current_user + @params = params + @action = determine_action + end + + def execute + return ServiceResponse.error(message: 'Admin is required') unless current_user&.can_admin_all_resources? + return ServiceResponse.error(message: 'Action is required') unless action.present? + + result = perform_action + if result[:status] == :success + event = close_report_and_record_event + ServiceResponse.success(message: event.success_message) + else + ServiceResponse.error(message: result[:message]) + end + end + + private + + def determine_action + action = params[:user_action] + if action.in?(ResourceEvents::AbuseReportEvent.actions.keys) + action.to_sym + elsif close_report? + :close_report + end + end + + def perform_action + case action + when :ban_user then ban_user + when :block_user then block_user + when :delete_user then delete_user + when :close_report then close_report + end + end + + def ban_user + Users::BanService.new(current_user).execute(abuse_report.user) + end + + def block_user + Users::BlockService.new(current_user).execute(abuse_report.user) + end + + def delete_user + abuse_report.user.delete_async(deleted_by: current_user) + success + end + + def close_report + return error('Report already closed') if abuse_report.closed? + + abuse_report.closed! + success + end + + def close_report_and_record_event + event = action + + if close_report? && action != :close_report + close_report + event = "#{action}_and_close_report" + end + + record_event(event) + end + + def close_report? + params[:close].to_s == 'true' + end + + def record_event(action) + reason = params[:reason] + unless reason.in?(ResourceEvents::AbuseReportEvent.reasons.keys) + reason = ResourceEvents::AbuseReportEvent.reasons[:other] + end + + abuse_report.events.create(action: action, user: current_user, reason: reason, comment: params[:comment]) + end + end + end +end diff --git a/app/views/admin/application_settings/appearances/_form.html.haml b/app/views/admin/application_settings/appearances/_form.html.haml index fb5c320268e..672af002e5e 100644 --- a/app/views/admin/application_settings/appearances/_form.html.haml +++ b/app/views/admin/application_settings/appearances/_form.html.haml @@ -3,146 +3,143 @@ = gitlab_ui_form_for @appearance, url: admin_application_settings_appearances_path, html: { class: 'gl-mt-3' } do |f| = form_errors(@appearance) - .row - .col-lg-4 - %h4.gl-mt-0= _('Navigation bar') - - .col-lg-8 - .form-group - = f.label :header_logo, _('Header logo'), class: 'col-form-label gl-pt-0' - %p - - if @appearance.header_logo? - = image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview' - - if @appearance.persisted? - %br - = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: header_logos_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Header logo will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove header logo') } }) do - = _('Remove header logo') - %hr - = f.hidden_field :header_logo_cache - = f.file_field :header_logo, class: "", accept: 'image/*' - .form-text.text-muted - = _('Maximum file size is 1MB. Pages are optimized for a 24px tall header logo') - %hr - .row - .col-lg-4 - %h4.gl-mt-0 Favicon - - .col-lg-8 - .form-group - = f.label :favicon, _('Favicon'), class: 'col-form-label gl-pt-0' - %p - - if @appearance.favicon? - = image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview' - - if @appearance.persisted? - %br - = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: favicon_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Favicon will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove favicon') } }) do - = _('Remove favicon') - %hr - = f.hidden_field :favicon_cache - = f.file_field :favicon, class: '', accept: 'image/*' - .form-text.text-muted - = _("Maximum file size is 1 MB. Image size must be 32 x 32 pixels. Allowed image formats are %{favicon_extension_allowlist}.") % { favicon_extension_allowlist: favicon_extension_allowlist } + .settings-section + .settings-sticky-header + .settings-sticky-header-inner + %h4.gl-my-0= _('Navigation bar') + + .form-group + = f.label :header_logo, _('Header logo'), class: 'col-form-label gl-pt-0' + %p + - if @appearance.header_logo? + = image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview' + - if @appearance.persisted? + %br + = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: header_logos_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Header logo will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove header logo') } }) do + = _('Remove header logo') + %hr + = f.hidden_field :header_logo_cache + = f.file_field :header_logo, class: "", accept: 'image/*' + .form-text.text-muted + = _('Maximum file size is 1MB. Pages are optimized for a 24px tall header logo') + + .settings-section + .settings-sticky-header + .settings-sticky-header-inner + %h4.gl-my-0 Favicon + + .form-group + = f.label :favicon, _('Favicon'), class: 'col-form-label gl-pt-0' + %p + - if @appearance.favicon? + = image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview' + - if @appearance.persisted? %br - = _("Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.") + = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: favicon_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Favicon will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove favicon') } }) do + = _('Remove favicon') + %hr + = f.hidden_field :favicon_cache + = f.file_field :favicon, class: '', accept: 'image/*' + .form-text.text-muted + = _("Maximum file size is 1 MB. Image size must be 32 x 32 pixels. Allowed image formats are %{favicon_extension_allowlist}.") % { favicon_extension_allowlist: favicon_extension_allowlist } + %br + = _("Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.") = render partial: 'admin/application_settings/appearances/system_header_footer_form', locals: { form: f } - %hr - .row - .col-lg-4 - %h4.gl-mt-0= _('Sign in/Sign up pages') - - .col-lg-8 - .form-group - = f.label :title, class: 'col-form-label' - = f.text_field :title, class: "form-control gl-form-input" - .form-group - = f.label :description, class: 'col-form-label' - = f.text_area :description, class: "form-control gl-form-input", rows: 10 + .settings-section + .settings-sticky-header + .settings-sticky-header-inner + %h4.gl-my-0= _('Sign in/Sign up pages') + + .form-group + = f.label :title, class: 'col-form-label' + = f.text_field :title, class: "form-control gl-form-input" + .form-group + = f.label :description, class: 'col-form-label' + = f.text_area :description, class: "form-control gl-form-input", rows: 10 + .form-text.text-muted + = parsed_with_gfm + .form-group + = f.label :logo, class: 'col-form-label gl-pt-0' + %p + - if @appearance.logo? + = image_tag @appearance.logo_path, class: 'appearance-logo-preview' + - if @appearance.persisted? + %br + = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: logo_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Logo will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove logo') } }) do + = _('Remove logo') + %hr + = f.hidden_field :logo_cache + = f.file_field :logo, class: "", accept: 'image/*' + .form-text.text-muted + = _('Maximum file size is 1 MB. Pages are optimized for a 128x128 px logo.') + + .settings-section + .settings-sticky-header + .settings-sticky-header-inner + %h4.gl-my-0= _('Progressive Web App (PWA)') + + .form-group + = f.label _("Name"), class: 'col-form-label' + = f.text_field :pwa_name, class: "form-control gl-form-input" + .form-group + = f.label _("Short name"), class: 'col-form-label' + = f.text_field :pwa_short_name, class: "form-control gl-form-input" + .form-group + = f.label _("Description"), class: 'col-form-label' + = f.text_area :pwa_description, class: "form-control gl-form-input", rows: 10 + .form-text.text-muted + = parsed_with_gfm + .form-group + = f.label :pwa_icon, class: 'col-form-label gl-pt-0' + %p + - if @appearance.pwa_icon? + = image_tag @appearance.pwa_icon_path, class: 'appearance-pwa-icon-preview' + - if @appearance.persisted? + %br + = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: pwa_icon_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Icon will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove icon') } }) do + = _('Remove icon') + %hr + = f.hidden_field :pwa_icon_cache + = f.file_field :pwa_icon, class: "", accept: 'image/*' + .form-text.text-muted + = _('Maximum file size is 1MB.') + + .settings-section + .settings-sticky-header + .settings-sticky-header-inner + %h4.gl-my-0= _('New project pages') + + .form-group + = f.label :new_project_guidelines, class: 'col-form-label' + %p + = f.text_area :new_project_guidelines, class: "form-control gl-form-input", rows: 10 .form-text.text-muted = parsed_with_gfm - .form-group - = f.label :logo, class: 'col-form-label gl-pt-0' - %p - - if @appearance.logo? - = image_tag @appearance.logo_path, class: 'appearance-logo-preview' - - if @appearance.persisted? - %br - = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: logo_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Logo will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove logo') } }) do - = _('Remove logo') - %hr - = f.hidden_field :logo_cache - = f.file_field :logo, class: "", accept: 'image/*' - .form-text.text-muted - = _('Maximum file size is 1 MB. Pages are optimized for a 128x128 px logo.') - - %hr - .row - .col-lg-4 - %h4.gl-mt-0= _('Progressive Web App (PWA)') - - .col-lg-8 - .form-group - = f.label _("Name"), class: 'col-form-label' - = f.text_field :pwa_name, class: "form-control gl-form-input" - .form-group - = f.label _("Short name"), class: 'col-form-label' - = f.text_field :pwa_short_name, class: "form-control gl-form-input" - .form-group - = f.label _("Description"), class: 'col-form-label' - = f.text_area :pwa_description, class: "form-control gl-form-input", rows: 10 + + .settings-section + .settings-sticky-header + .settings-sticky-header-inner + %h4.gl-my-0= _('Profile image guideline') + + .form-group + = f.label :profile_image_guidelines, class: 'col-form-label' + %p + = f.text_area :profile_image_guidelines, class: "form-control gl-form-input", rows: 10 .form-text.text-muted = parsed_with_gfm - .form-group - = f.label :pwa_icon, class: 'col-form-label gl-pt-0' - %p - - if @appearance.pwa_icon? - = image_tag @appearance.pwa_icon_path, class: 'appearance-pwa-icon-preview' - - if @appearance.persisted? - %br - = render Pajamas::ButtonComponent.new(variant: :danger, category: :secondary, size: :small, method: :delete, href: pwa_icon_admin_application_settings_appearances_path, button_options: { data: { confirm: _("Icon will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove icon') } }) do - = _('Remove icon') - %hr - = f.hidden_field :pwa_icon_cache - = f.file_field :pwa_icon, class: "", accept: 'image/*' - .form-text.text-muted - = _('Maximum file size is 1MB.') - - %hr - .row - .col-lg-4 - %h4.gl-mt-0= _('New project pages') - - .col-lg-8 - .form-group - = f.label :new_project_guidelines, class: 'col-form-label' - %p - = f.text_area :new_project_guidelines, class: "form-control gl-form-input", rows: 10 - .form-text.text-muted - = parsed_with_gfm - - %hr - .row - .col-lg-4 - %h4.gl-mt-0= _('Profile image guideline') - - .col-lg-8 - .form-group - = f.label :profile_image_guidelines, class: 'col-form-label' - %p - = f.text_area :profile_image_guidelines, class: "form-control gl-form-input", rows: 10 - .form-text.text-muted - = parsed_with_gfm - - .gl-mt-3.gl-mb-3 - = f.submit _('Update appearance settings'), pajamas_button: true - - if @appearance.persisted? || @appearance.updated_at - .mt-4 - - if @appearance.persisted? - Preview last save: - = link_to _('Sign-in page'), preview_sign_in_admin_application_settings_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' - = link_to _('New project page'), new_project_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' - - - if @appearance.updated_at - %span.float-right - Last edit #{time_ago_with_tooltip(@appearance.updated_at)} + + - if @appearance.persisted? || @appearance.updated_at + .settings-section + - if @appearance.persisted? + Preview last save: + = link_to _('Sign-in page'), preview_sign_in_admin_application_settings_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' + = link_to _('New project page'), new_project_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' + + - if @appearance.updated_at + %span.float-right + Last edit #{time_ago_with_tooltip(@appearance.updated_at)} + + .settings-sticky-footer + = f.submit _('Update appearance settings'), pajamas_button: true diff --git a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml index 2ca037db532..61df5f5fd0d 100644 --- a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml +++ b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml @@ -1,30 +1,29 @@ - form = local_assigns.fetch(:form) -%hr -.row - .col-lg-4 - %h4.gl-mt-0 - = _('System header and footer') +.settings-section + .settings-sticky-header + .settings-sticky-header-inner + %h4.gl-my-0 + = _('System header and footer') - .col-lg-8 - .form-group - = form.label :header_message, _('Header message'), class: 'col-form-label label-bold' - = form.text_area :header_message, placeholder: _('State your message to activate'), class: "form-control gl-form-input js-autosize" - .form-group - = form.label :footer_message, _('Footer message'), class: 'col-form-label label-bold' - = form.text_area :footer_message, placeholder: _('State your message to activate'), class: "form-control gl-form-input js-autosize" - .form-group - = form.gitlab_ui_checkbox_component :email_header_and_footer_enabled, - _('Enable header and footer in emails'), - help_text: _('Add header and footer to emails. Please note that color settings will only be applied within the application interface'), - label_options: { class: 'gl-font-weight-bold!' } + .form-group + = form.label :header_message, _('Header message'), class: 'col-form-label label-bold' + = form.text_area :header_message, placeholder: _('State your message to activate'), class: "form-control gl-form-input js-autosize" + .form-group + = form.label :footer_message, _('Footer message'), class: 'col-form-label label-bold' + = form.text_area :footer_message, placeholder: _('State your message to activate'), class: "form-control gl-form-input js-autosize" + .form-group + = form.gitlab_ui_checkbox_component :email_header_and_footer_enabled, + _('Enable header and footer in emails'), + help_text: _('Add header and footer to emails. Please note that color settings will only be applied within the application interface'), + label_options: { class: 'gl-font-weight-bold!' } - .form-group.js-toggle-colors-container - = render Pajamas::ButtonComponent.new(variant: :link, button_options: { class: 'js-toggle-colors-link' }) do - = _('Customize colors') - .form-group.js-toggle-colors-container.hide - = form.label :message_background_color, _('Background Color'), class: 'col-form-label label-bold' - = form.color_field :message_background_color, class: "form-control gl-form-input" - .form-group.js-toggle-colors-container.hide - = form.label :message_font_color, _('Font Color'), class: 'col-form-label label-bold' - = form.color_field :message_font_color, class: "form-control gl-form-input" + .form-group.js-toggle-colors-container + = render Pajamas::ButtonComponent.new(variant: :link, button_options: { class: 'js-toggle-colors-link' }) do + = _('Customize colors') + .form-group.js-toggle-colors-container.hide + = form.label :message_background_color, _('Background Color'), class: 'col-form-label label-bold' + = form.color_field :message_background_color, class: "form-control gl-form-input" + .form-group.js-toggle-colors-container.hide + = form.label :message_font_color, _('Font Color'), class: 'col-form-label label-bold' + = form.color_field :message_font_color, class: "form-control gl-form-input" diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index a03d6cb5a94..3d73b255a5e 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -1,11 +1,9 @@ - page_title _('New Deploy Key') %h1.page-title.gl-font-size-h-display= _('New public deploy key') -%hr -%div - = gitlab_ui_form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f| - = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } - .form-actions - = f.submit 'Create', data: { qa_selector: "add_deploy_key_button" }, pajamas_button: true - = render Pajamas::ButtonComponent.new(href: admin_deploy_keys_path) do - = _('Cancel') += gitlab_ui_form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f| + = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } + .gl-display-flex.gl-mt-6.gl-gap-3 + = f.submit 'Create', data: { qa_selector: "add_deploy_key_button" }, pajamas_button: true + = render Pajamas::ButtonComponent.new(href: admin_deploy_keys_path) do + = _('Cancel') diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index e7ea139eb71..397ba7ae700 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -40,7 +40,7 @@ %p.gl-text-secondary = s_('Preferences|Customize the appearance of the syntax.') = succeed '.' do - = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank', rel: 'noopener noreferrer' + = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'change-the-syntax-highlighting-theme'), target: '_blank', rel: 'noopener noreferrer' .syntax-theme.row - Gitlab::ColorSchemes.each do |scheme| %label.col-6.col-sm-4.col-md-3.col-lg-auto.gl-mb-5 diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml index 997443d5fa9..0044ff4dc24 100644 --- a/app/views/projects/deploy_keys/edit.html.haml +++ b/app/views/projects/deploy_keys/edit.html.haml @@ -1,10 +1,8 @@ - page_title _('Edit Deploy Key') %h1.page-title.gl-font-size-h-display= _('Edit Deploy Key') -%hr -%div - = gitlab_ui_form_for [@project, @deploy_key], include_id: false, html: { class: 'js-requires-input' } do |f| - = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } - .form-actions - = f.submit _('Save changes'), pajamas_button: true - = link_button_to _('Cancel'), project_settings_repository_path(@project) += gitlab_ui_form_for [@project, @deploy_key], include_id: false, html: { class: 'js-requires-input' } do |f| + = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } + .gl-display-flex.gl-mt-6.gl-gap-3 + = f.submit _('Save changes'), pajamas_button: true + = link_button_to _('Cancel'), project_settings_repository_path(@project, anchor: 'js-deploy-keys-settings') diff --git a/app/views/projects/issues/service_desk.html.haml b/app/views/projects/issues/service_desk.html.haml index 9793f21e4a9..2d17719a8c2 100644 --- a/app/views/projects/issues/service_desk.html.haml +++ b/app/views/projects/issues/service_desk.html.haml @@ -15,7 +15,7 @@ can_edit_project_settings: can?(current_user, :admin_project, @project).to_s, service_desk_callout_svg_path: image_path('service_desk_callout.svg'), service_desk_settings_path: edit_project_path(@project, anchor: 'js-service-desk'), - service_desk_help_path: help_page_path('user/project/service_desk'), + service_desk_help_path: help_page_path('user/project/service_desk/index'), is_service_desk_supported: Gitlab::ServiceDesk.supported?.to_s, is_service_desk_enabled: @project.service_desk_enabled?.to_s } } - else diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml index 280362a12a9..bbaf5bf9627 100644 --- a/app/views/shared/deploy_keys/_form.html.haml +++ b/app/views/shared/deploy_keys/_form.html.haml @@ -5,37 +5,34 @@ = form_errors(deploy_key) .form-group - = form.label :title, class: 'col-form-label col-sm-2' - .col-sm-10= form.text_field :title, class: 'form-control gl-form-input', data: { testid: 'deploy-key-title-field' }, readonly: ('readonly' unless can?(current_user, :update_deploy_key, deploy_key)) + = form.label :title + = form.text_field :title, class: 'form-control gl-form-input', data: { testid: 'deploy-key-title-field' }, readonly: ('readonly' unless can?(current_user, :update_deploy_key, deploy_key)) -.form-group - - if deploy_key.new_record? - = form.label :key, class: 'col-form-label col-sm-2' - .col-sm-10 - %p.light - - link_start = "<a href='#{help_page_path('user/ssh')}' target='_blank' rel='noreferrer noopener'>".html_safe - - link_end = '</a>' - = _('Paste a public key here. %{link_start}How do I generate it?%{link_end}').html_safe % { link_start: link_start, link_end: link_end.html_safe } - = form.text_area :key, class: 'form-control gl-form-input thin_area', rows: 5, data: { testid: 'deploy-key-field' } - - else - - if deploy_key.fingerprint_sha256.present? - = form.label :fingerprint, _('Fingerprint (SHA256)'), class: 'col-form-label col-sm-2' - .col-sm-10 - = form.text_field :fingerprint_sha256, class: 'form-control gl-form-input', readonly: 'readonly' - - if deploy_key.fingerprint.present? - = form.label :fingerprint, _('Fingerprint (MD5)'), class: 'col-form-label col-sm-2' - .col-sm-10 - = form.text_field :fingerprint, class: 'form-control gl-form-input', readonly: 'readonly' +- if deploy_key.new_record? + .form-group + = form.label :key + + %p.gl-text-secondary + - link_start = "<a href='#{help_page_path('user/ssh')}' target='_blank' rel='noreferrer noopener'>".html_safe + - link_end = '</a>' + = _('Paste a public key here. %{link_start}How do I generate it?%{link_end}').html_safe % { link_start: link_start, link_end: link_end.html_safe } + = form.text_area :key, class: 'form-control gl-form-input thin_area', rows: 5, data: { testid: 'deploy-key-field' } +- else + - if deploy_key.fingerprint_sha256.present? + .form-group + = form.label :fingerprint, _('Fingerprint (SHA256)') + = form.text_field :fingerprint_sha256, class: 'form-control gl-form-input', readonly: 'readonly' + - if deploy_key.fingerprint.present? + .form-group + = form.label :fingerprint, _('Fingerprint (MD5)') + = form.text_field :fingerprint, class: 'form-control gl-form-input', readonly: 'readonly' .form-group - .col-sm-10 - = form.label :expires_at, _('Expiration date (optional)'), class: 'label-bold' - = form.text_field :expires_at, class: 'form-control gl-form-input', readonly: 'readonly' + = form.label :expires_at, _('Expiration date (optional)'), class: 'label-bold' + = form.text_field :expires_at, class: 'form-control gl-form-input', readonly: 'readonly' - if deploy_keys_project.present? = form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form| .form-group - .col-form-label.col-sm-2 - .col-sm-10 - = deploy_keys_project_form.gitlab_ui_checkbox_component :can_push, _('Grant write permissions to this key'), - help_text: _('Allow this key to push to this repository') + = deploy_keys_project_form.gitlab_ui_checkbox_component :can_push, _('Grant write permissions to this key'), + help_text: _('Allow this key to push to this repository') diff --git a/app/views/shared/deploy_keys/_index.html.haml b/app/views/shared/deploy_keys/_index.html.haml index 32720d0353b..3f81cbbb9b1 100644 --- a/app/views/shared/deploy_keys/_index.html.haml +++ b/app/views/shared/deploy_keys/_index.html.haml @@ -4,11 +4,13 @@ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Deploy keys') = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do = expanded ? _('Collapse') : _('Expand') - %p + %p.gl-text-secondary.gl-mb-0 - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/deploy_keys/index') } = _("Add deploy keys to grant read/write access to this repository. %{link_start}What are deploy keys?%{link_end}").html_safe % { link_start: link_start, link_end: '</a>'.html_safe } .settings-content - %h5.gl-mt-0 - = render @deploy_keys.form_partial_path - %hr - #js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project), project_id: @project.id } } + = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card js-toggle-container' }, body_options: { class: 'gl-new-card-body gl-px-0' }) do |c| + - c.with_body do + .gl-new-card-add-form.gl-m-3.gl-display-none.js-toggle-content + = render @deploy_keys.form_partial_path + + #js-deploy-keys{ data: { endpoint: project_deploy_keys_path(@project), project_id: @project.id } } diff --git a/app/views/shared/deploy_keys/_project_group_form.html.haml b/app/views/shared/deploy_keys/_project_group_form.html.haml index 2f90345ed12..c633088b26a 100644 --- a/app/views/shared/deploy_keys/_project_group_form.html.haml +++ b/app/views/shared/deploy_keys/_project_group_form.html.haml @@ -1,5 +1,9 @@ = gitlab_ui_form_for [@project.namespace, @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input container" } do |f| = form_errors(@deploy_keys.new_key) + + .form-group.row + %h4.gl-my-0= s_('DeployKeys|Add new deploy key') + .form-group.row = f.label :title, class: "label-bold" = f.text_field :title, class: 'form-control gl-form-input', required: true, data: { testid: 'deploy-key-title-field' } @@ -20,5 +24,7 @@ = f.gitlab_ui_datepicker :expires_at, data: { testid: 'deploy-key-expires-at-field' }, value: f.object.expires_at %p.form-text.text-muted= ssh_key_expires_field_description - .form-group.row + .form-group.row.gl-mb-0 = f.submit _("Add key"), data: { testid: "add-deploy-key-button"}, pajamas_button: true + = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-3 js-toggle-button' }) do + = _('Cancel') |