Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-11 00:08:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-11 00:08:21 +0300
commite43574ee831197b604c59b80f94e30764223e5ed (patch)
treef467f4c23cd9d151b5d54711451252de94d62879 /app
parentd18b7dc5eea84db5008986c6879a24ad7f6462a6 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/invite_members/components/invite_modal_base.vue185
-rw-r--r--app/assets/javascripts/tracking/tracking.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/content_transition.vue32
-rw-r--r--app/controllers/groups/harbor/repositories_controller.rb24
-rw-r--r--app/controllers/projects/harbor/application_controller.rb22
-rw-r--r--app/controllers/projects/harbor/repositories_controller.rb11
-rw-r--r--app/models/user.rb2
-rw-r--r--app/presenters/ci/build_runner_presenter.rb57
-rw-r--r--app/services/ci/runners/unassign_runner_service.rb9
-rw-r--r--app/services/users/destroy_service.rb2
-rw-r--r--app/services/users/migrate_to_ghost_user_service.rb13
-rw-r--r--app/views/dashboard/todos/_todo.html.haml6
-rw-r--r--app/views/dashboard/todos/index.html.haml4
-rw-r--r--app/views/groups/harbor/repositories/index.html.haml9
-rw-r--r--app/views/projects/blob/_editor.html.haml3
-rw-r--r--app/views/projects/blob/_upload.html.haml4
-rw-r--r--app/views/projects/commit/_commit_box.html.haml4
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/harbor/repositories/index.html.haml9
19 files changed, 282 insertions, 122 deletions
diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
index 69205d7d250..bafbe94b8bd 100644
--- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue
+++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue
@@ -11,6 +11,7 @@ import {
GlFormInput,
} from '@gitlab/ui';
import { sprintf } from '~/locale';
+import ContentTransition from '~/vue_shared/components/content_transition.vue';
import {
ACCESS_LEVEL,
ACCESS_EXPIRE_DATE,
@@ -20,6 +21,17 @@ import {
HEADER_CLOSE_LABEL,
} from '../constants';
+const DEFAULT_SLOT = 'default';
+const DEFAULT_SLOTS = [
+ {
+ key: DEFAULT_SLOT,
+ attributes: {
+ class: 'invite-modal-content',
+ 'data-testid': 'invite-modal-initial-content',
+ },
+ },
+];
+
export default {
components: {
GlFormGroup,
@@ -31,6 +43,7 @@ export default {
GlSprintf,
GlButton,
GlFormInput,
+ ContentTransition,
},
inheritAttrs: false,
props: {
@@ -86,6 +99,21 @@ export default {
required: false,
default: '',
},
+ submitButtonText: {
+ type: String,
+ required: false,
+ default: INVITE_BUTTON_TEXT,
+ },
+ currentSlot: {
+ type: String,
+ required: false,
+ default: DEFAULT_SLOT,
+ },
+ extraSlots: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
// Be sure to check out reset!
@@ -110,6 +138,9 @@ export default {
(key) => this.accessLevels[key] === Number(this.selectedAccessLevel),
);
},
+ contentSlots() {
+ return [...DEFAULT_SLOTS, ...(this.extraSlots || [])];
+ },
},
watch: {
selectedAccessLevel: {
@@ -148,6 +179,7 @@ export default {
READ_MORE_TEXT,
INVITE_BUTTON_TEXT,
CANCEL_BUTTON_TEXT,
+ DEFAULT_SLOT,
};
</script>
@@ -164,79 +196,96 @@ export default {
@close="reset"
@hide="reset"
>
- <div class="gl-display-flex" data-testid="modal-base-intro-text">
- <slot name="intro-text-before"></slot>
- <p>
- <gl-sprintf :message="introText">
- <template #strong="{ content }">
- <strong>{{ content }}</strong>
- </template>
- </gl-sprintf>
- </p>
- <slot name="intro-text-after"></slot>
- </div>
-
- <gl-form-group
- :invalid-feedback="invalidFeedbackMessage"
- :state="validationState"
- :description="formGroupDescription"
- data-testid="members-form-group"
+ <content-transition
+ class="gl-display-grid"
+ transition-name="invite-modal-transition"
+ :slots="contentSlots"
+ :current-slot="currentSlot"
>
- <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
- <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
- </gl-form-group>
+ <template #[$options.DEFAULT_SLOT]>
+ <div class="gl-display-flex" data-testid="modal-base-intro-text">
+ <slot name="intro-text-before"></slot>
+ <p>
+ <gl-sprintf :message="introText">
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </p>
+ <slot name="intro-text-after"></slot>
+ </div>
- <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-dropdown
- class="gl-shadow-none gl-w-full"
- data-qa-selector="access_level_dropdown"
- v-bind="$attrs"
- :text="selectedRoleName"
- >
- <template v-for="(key, item) in accessLevels">
- <gl-dropdown-item
- :key="key"
- active-class="is-active"
- is-check-item
- :is-checked="key === selectedAccessLevel"
- @click="changeSelectedItem(key)"
- >
- <div>{{ item }}</div>
- </gl-dropdown-item>
- </template>
- </gl-dropdown>
- </div>
+ <gl-form-group
+ :invalid-feedback="invalidFeedbackMessage"
+ :state="validationState"
+ :description="formGroupDescription"
+ data-testid="members-form-group"
+ >
+ <label :id="selectLabelId" class="col-form-label">{{ labelSearchField }}</label>
+ <slot name="select" v-bind="{ validationState, labelId: selectLabelId }"></slot>
+ </gl-form-group>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full">
- <gl-sprintf :message="$options.READ_MORE_TEXT">
- <template #link="{ content }">
- <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
+ <label class="gl-font-weight-bold">{{ $options.ACCESS_LEVEL }}</label>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-dropdown
+ class="gl-shadow-none gl-w-full"
+ data-qa-selector="access_level_dropdown"
+ v-bind="$attrs"
+ :text="selectedRoleName"
+ >
+ <template v-for="(key, item) in accessLevels">
+ <gl-dropdown-item
+ :key="key"
+ active-class="is-active"
+ is-check-item
+ :is-checked="key === selectedAccessLevel"
+ @click="changeSelectedItem(key)"
+ >
+ <div>{{ item }}</div>
+ </gl-dropdown-item>
+ </template>
+ </gl-dropdown>
+ </div>
- <label class="gl-mt-5 gl-display-block" for="expires_at">{{
- $options.ACCESS_EXPIRE_DATE
- }}</label>
- <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
- <gl-datepicker
- v-model="selectedDate"
- class="gl-display-inline!"
- :min-date="minDate"
- :target="null"
- >
- <template #default="{ formattedDate }">
- <gl-form-input class="gl-w-full" :value="formattedDate" :placeholder="__(`YYYY-MM-DD`)" />
- </template>
- </gl-datepicker>
- </div>
- <slot name="form-after"></slot>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full">
+ <gl-sprintf :message="$options.READ_MORE_TEXT">
+ <template #link="{ content }">
+ <gl-link :href="helpLink" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+ <label class="gl-mt-5 gl-display-block" for="expires_at">{{
+ $options.ACCESS_EXPIRE_DATE
+ }}</label>
+ <div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
+ <gl-datepicker
+ v-model="selectedDate"
+ class="gl-display-inline!"
+ :min-date="minDate"
+ :target="null"
+ >
+ <template #default="{ formattedDate }">
+ <gl-form-input
+ class="gl-w-full"
+ :value="formattedDate"
+ :placeholder="__(`YYYY-MM-DD`)"
+ />
+ </template>
+ </gl-datepicker>
+ </div>
+ <slot name="form-after"></slot>
+ </template>
+ <template v-for="{ key } in extraSlots" #[key]>
+ <slot :name="key"></slot>
+ </template>
+ </content-transition>
<template #modal-footer>
- <gl-button data-testid="cancel-button" @click="closeModal">
- {{ $options.CANCEL_BUTTON_TEXT }}
- </gl-button>
+ <slot name="cancel-button">
+ <gl-button data-testid="cancel-button" @click="closeModal">
+ {{ $options.CANCEL_BUTTON_TEXT }}
+ </gl-button>
+ </slot>
<gl-button
:disabled="submitDisabled"
:loading="isLoading"
@@ -245,7 +294,7 @@ export default {
data-testid="invite-button"
@click="submit"
>
- {{ $options.INVITE_BUTTON_TEXT }}
+ {{ submitButtonText }}
</gl-button>
</template>
</gl-modal>
diff --git a/app/assets/javascripts/tracking/tracking.js b/app/assets/javascripts/tracking/tracking.js
index c26abc261ed..173eef0646b 100644
--- a/app/assets/javascripts/tracking/tracking.js
+++ b/app/assets/javascripts/tracking/tracking.js
@@ -10,6 +10,8 @@ import {
addReferrersCacheEntry,
} from './utils';
+const ALLOWED_URL_HASHES = ['#diff', '#note'];
+
export default class Tracking {
static queuedEvents = [];
static initialized = false;
@@ -183,7 +185,9 @@ export default class Tracking {
originalUrl: window.location.href,
});
- window.snowplow('setCustomUrl', pageLinks.url);
+ const appendHash = ALLOWED_URL_HASHES.some((prefix) => window.location.hash.startsWith(prefix));
+ const customUrl = `${pageUrl}${appendHash ? window.location.hash : ''}`;
+ window.snowplow('setCustomUrl', customUrl);
if (document.referrer) {
const node = referrers.find((links) => links.originalUrl === document.referrer);
diff --git a/app/assets/javascripts/vue_shared/components/content_transition.vue b/app/assets/javascripts/vue_shared/components/content_transition.vue
new file mode 100644
index 00000000000..446610d6b91
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_transition.vue
@@ -0,0 +1,32 @@
+<script>
+export default {
+ props: {
+ currentSlot: {
+ type: String,
+ required: true,
+ },
+ slots: {
+ type: Array,
+ required: true,
+ },
+ transitionName: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ shouldShow(key) {
+ return this.currentSlot === key;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <transition v-for="{ key, attributes } in slots" :key="key" :name="transitionName">
+ <div v-show="shouldShow(key)" v-bind="attributes">
+ <slot :name="key"></slot>
+ </div>
+ </transition>
+ </div>
+</template>
diff --git a/app/controllers/groups/harbor/repositories_controller.rb b/app/controllers/groups/harbor/repositories_controller.rb
new file mode 100644
index 00000000000..364607f9b20
--- /dev/null
+++ b/app/controllers/groups/harbor/repositories_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Groups
+ module Harbor
+ class RepositoriesController < Groups::ApplicationController
+ feature_category :integrations
+
+ before_action :harbor_registry_enabled!
+ before_action do
+ push_frontend_feature_flag(:harbor_registry_integration)
+ end
+
+ def show
+ render :index
+ end
+
+ private
+
+ def harbor_registry_enabled!
+ render_404 unless Feature.enabled?(:harbor_registry_integration)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/harbor/application_controller.rb b/app/controllers/projects/harbor/application_controller.rb
new file mode 100644
index 00000000000..e6e694783fa
--- /dev/null
+++ b/app/controllers/projects/harbor/application_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Projects
+ module Harbor
+ class ApplicationController < Projects::ApplicationController
+ layout 'project'
+
+ before_action :harbor_registry_enabled!
+ before_action do
+ push_frontend_feature_flag(:harbor_registry_integration)
+ end
+
+ feature_category :integrations
+
+ private
+
+ def harbor_registry_enabled!
+ render_404 unless Feature.enabled?(:harbor_registry_integration)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/harbor/repositories_controller.rb b/app/controllers/projects/harbor/repositories_controller.rb
new file mode 100644
index 00000000000..dd3e3dc1978
--- /dev/null
+++ b/app/controllers/projects/harbor/repositories_controller.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Projects
+ module Harbor
+ class RepositoriesController < ::Projects::Harbor::ApplicationController
+ def show
+ render :index
+ end
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index e49a39079ef..fa58455ad35 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -391,7 +391,7 @@ class User < ApplicationRecord
# rubocop: disable CodeReuse/ServiceClass
# Ideally we should not call a service object here but user.block
- # is also bcalled by Users::MigrateToGhostUserService which references
+ # is also called by Users::MigrateToGhostUserService which references
# this state transition object in order to do a rollback.
# For this reason the tradeoff is to disable this cop.
after_transition any => :blocked do |user|
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 015dfc16df0..082993130a1 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -64,50 +64,35 @@ module Ci
def create_archive(artifacts)
return unless artifacts[:untracked] || artifacts[:paths]
- BuildArtifact.for_archive(artifacts).to_h.tap do |artifact|
- artifact.delete(:exclude) unless artifact[:exclude].present?
+ archive = {
+ artifact_type: :archive,
+ artifact_format: :zip,
+ name: artifacts[:name],
+ untracked: artifacts[:untracked],
+ paths: artifacts[:paths],
+ when: artifacts[:when],
+ expire_in: artifacts[:expire_in]
+ }
+
+ if artifacts.dig(:exclude).present?
+ archive.merge(exclude: artifacts[:exclude])
+ else
+ archive
end
end
def create_reports(reports, expire_in:)
return unless reports&.any?
- reports.map { |report| BuildArtifact.for_report(report, expire_in).to_h.compact }
- end
-
- BuildArtifact = Struct.new(:name, :untracked, :paths, :exclude, :when, :expire_in, :artifact_type, :artifact_format, keyword_init: true) do
- def self.for_archive(artifacts)
- self.new(
- artifact_type: :archive,
- artifact_format: :zip,
- name: artifacts[:name],
- untracked: artifacts[:untracked],
- paths: artifacts[:paths],
- when: artifacts[:when],
- expire_in: artifacts[:expire_in],
- exclude: artifacts[:exclude]
- )
- end
-
- def self.for_report(report, expire_in)
- type, params = report
-
- if type == :coverage_report
- artifact_type = params[:coverage_format].to_sym
- paths = [params[:path]]
- else
- artifact_type = type
- paths = params
- end
-
- self.new(
- artifact_type: artifact_type,
- artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(artifact_type),
- name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(artifact_type),
- paths: paths,
+ reports.map do |report_type, report_paths|
+ {
+ artifact_type: report_type.to_sym,
+ artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym),
+ name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym),
+ paths: report_paths,
when: 'always',
expire_in: expire_in
- )
+ }
end
end
diff --git a/app/services/ci/runners/unassign_runner_service.rb b/app/services/ci/runners/unassign_runner_service.rb
index a38a4196021..1e46cf6add8 100644
--- a/app/services/ci/runners/unassign_runner_service.rb
+++ b/app/services/ci/runners/unassign_runner_service.rb
@@ -6,8 +6,9 @@ module Ci
# @param [Ci::RunnerProject] runner_project the runner/project association to destroy
# @param [User] user the user performing the operation
def initialize(runner_project, user)
- @runner = runner_project.runner
@runner_project = runner_project
+ @runner = runner_project.runner
+ @project = runner_project.project
@user = user
end
@@ -16,6 +17,12 @@ module Ci
@runner_project.destroy
end
+
+ private
+
+ attr_reader :runner, :project, :user
end
end
end
+
+Ci::Runners::UnassignRunnerService.prepend_mod
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 4ec875098fa..46eec082125 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -54,7 +54,7 @@ module Users
yield(user) if block_given?
- MigrateToGhostUserService.new(user).execute unless options[:hard_delete]
+ MigrateToGhostUserService.new(user).execute(hard_delete: options[:hard_delete])
response = Snippets::BulkDestroyService.new(current_user, user.snippets).execute(options)
raise DestroyError, response.message if response.error?
diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb
index 515d7821416..575614e8743 100644
--- a/app/services/users/migrate_to_ghost_user_service.rb
+++ b/app/services/users/migrate_to_ghost_user_service.rb
@@ -10,14 +10,21 @@ module Users
class MigrateToGhostUserService
extend ActiveSupport::Concern
- attr_reader :ghost_user, :user
+ attr_reader :ghost_user, :user, :hard_delete
def initialize(user)
@user = user
@ghost_user = User.ghost
end
- def execute
+ # If an admin attempts to hard delete a user, in some cases associated
+ # records may have a NOT NULL constraint on the user ID that prevent that record
+ # from being destroyed. In such situations we must assign the record to the ghost user.
+ # Passing in `hard_delete: true` will ensure these records get assigned to
+ # the ghost user before the user is destroyed. Other associated records will be destroyed.
+ # letting the other associated records be destroyed.
+ def execute(hard_delete: false)
+ @hard_delete = hard_delete
transition = user.block_transition
# Block the user before moving records to prevent a data race.
@@ -46,6 +53,8 @@ module Users
private
def migrate_records
+ return if hard_delete
+
migrate_issues
migrate_merge_requests
migrate_notes
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 2c6c721a51c..766900b7a99 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -50,12 +50,12 @@
.todo-actions.gl-ml-3
- if todo.pending?
= link_to dashboard_todo_path(todo), method: :delete, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-done-todo', data: { href: dashboard_todo_path(todo) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
Done
- %span.gl-spinner.ml-1
= link_to restore_dashboard_todo_path(todo), method: :patch, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-undo-todo hidden', data: { href: restore_dashboard_todo_path(todo) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
Undo
- %span.gl-spinner.ml-1
- else
= link_to restore_dashboard_todo_path(todo), method: :patch, class: 'gl-button btn btn-default btn-loading d-flex align-items-center js-add-todo', data: { href: restore_dashboard_todo_path(todo) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
Add a to do
- %span.gl-spinner.ml-1
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f6dc62e1d44..7a329fb34a9 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -22,11 +22,11 @@
- if @allowed_todos.any?(&:pending?)
.gl-mr-3
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
= s_("Todos|Mark all as done")
- %span.gl-spinner.ml-1
= link_to bulk_restore_dashboard_todos_path, class: 'gl-button btn btn-default btn-loading align-items-center js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2')
= s_("Todos|Undo mark all as done")
- %span.gl-spinner.ml-1
.todos-filters
.issues-details-filters.row-content-block.second-block
diff --git a/app/views/groups/harbor/repositories/index.html.haml b/app/views/groups/harbor/repositories/index.html.haml
new file mode 100644
index 00000000000..1ee15557e21
--- /dev/null
+++ b/app/views/groups/harbor/repositories/index.html.haml
@@ -0,0 +1,9 @@
+- page_title _("Harbor Registry")
+- @content_class = "limit-container-width" unless fluid_layout
+
+#js-harbor-registry-list-group{ data: { endpoint: group_harbor_registries_path(@group),
+ "no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
+ "containers_error_image" => image_path('illustrations/docker-error-state.svg'),
+ "help_page_path" => help_page_path('user/packages/container_registry/index'),
+ connection_error: (!!@connection_error).to_s,
+ invalid_path_error: (!!@invalid_path_error).to_s, } }
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 41333c416de..c9303e19d5d 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -45,5 +45,4 @@
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
- %h2
- %i.icon-spinner.icon-spin
+ = gl_loading_icon(size: 'lg')
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 6d2751bb7d4..1d3bec1ad44 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -20,8 +20,8 @@
= render 'shared/new_commit_form', placeholder: placeholder, ref: local_assigns[:ref]
.form-actions
- = button_tag class: 'btn gl-button btn-confirm btn-upload-file', id: 'submit-all', type: 'button' do
- .gl-spinner.gl-mr-2.js-loading-icon.hidden
+ = button_tag class: 'btn gl-button btn-confirm btn-upload-file gl-mr-2', id: 'submit-all', type: 'button' do
+ = gl_loading_icon(inline: true, css_class: 'gl-mr-2 js-loading-icon hidden')
= button_title
= link_to _("Cancel"), '#', class: "btn gl-button btn-default btn-cancel", "data-dismiss" => "modal"
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 36d3520cb59..a3343aa4228 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -37,13 +37,13 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha"
.commit-info.branches
- .gl-spinner.vertical-align-middle
+ = gl_loading_icon(inline: true, css_class: 'gl-vertical-align-middle')
.well-segment.merge-request-info
.icon-container
= custom_icon('mr_bold')
%span.commit-info.merge-requests{ 'data-project-commit-path' => merge_requests_project_commit_path(@project, @commit.id, format: :json) }
- .gl-spinner.vertical-align-middle
+ = gl_loading_icon(inline: true, css_class: 'gl-vertical-align-middle')
- if can?(current_user, :read_pipeline, @last_pipeline)
.well-segment.pipeline-info
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f32514141c5..0d1b838906c 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -107,6 +107,6 @@
.save-project-loader.hide
.center
%h2
- .gl-spinner.gl-spinner-md.align-text-bottom
+ = gl_loading_icon(inline: true, size: 'md', css_class: 'gl-vertical-align-middle')
= _('Saving project.')
%p= _('Please wait a moment, this page will automatically refresh when ready.')
diff --git a/app/views/projects/harbor/repositories/index.html.haml b/app/views/projects/harbor/repositories/index.html.haml
new file mode 100644
index 00000000000..b3f5b91596d
--- /dev/null
+++ b/app/views/projects/harbor/repositories/index.html.haml
@@ -0,0 +1,9 @@
+- page_title _("Harbor Registry")
+- @content_class = "limit-container-width" unless fluid_layout
+
+#js-harbor-registry-list-project{ data: { endpoint: project_harbor_registry_index_path(@project),
+ "no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
+ "containers_error_image" => image_path('illustrations/docker-error-state.svg'),
+ "help_page_path" => help_page_path('user/packages/container_registry/index'),
+ connection_error: (!!@connection_error).to_s,
+ invalid_path_error: (!!@invalid_path_error).to_s, } }