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>2023-12-18 21:16:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-18 21:16:08 +0300
commit5ba99858f15c33bf96f94cc5e9663f01c3532689 (patch)
treed9a4f77242ff797c6cdbf8a12f6644d347cf8935 /app
parent9ee9d3012b3747270beb6dec6200d632d8afd0e3 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue7
-rw-r--r--app/assets/javascripts/ci/catalog/constants.js34
-rw-r--r--app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_components.query.graphql18
-rw-r--r--app/assets/javascripts/ci/catalog/graphql/settings.js6
-rw-r--r--app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue11
-rw-r--r--app/controllers/explore/catalog_controller.rb3
-rw-r--r--app/controllers/projects/gcp/artifact_registry/base_controller.rb43
-rw-r--r--app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb131
-rw-r--r--app/controllers/projects/gcp/artifact_registry/setup_controller.rb11
-rw-r--r--app/models/integrations/asana.rb4
-rw-r--r--app/models/integrations/assembla.rb2
-rw-r--r--app/views/groups/_home_panel.html.haml15
-rw-r--r--app/views/projects/_home_panel.html.haml23
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml33
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml1
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml13
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/index.html.haml23
-rw-r--r--app/views/projects/gcp/artifact_registry/setup/new.html.haml31
18 files changed, 333 insertions, 76 deletions
diff --git a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue
index 91aaf2237b7..6d062d8b7f1 100644
--- a/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue
+++ b/app/assets/javascripts/ci/catalog/components/details/ci_resource_components.vue
@@ -31,7 +31,7 @@ export default {
};
},
update(data) {
- return data?.ciCatalogResource?.components?.nodes || [];
+ return data?.ciCatalogResource?.latestVersion?.components?.nodes || [];
},
error() {
createAlert({ message: this.$options.i18n.fetchError });
@@ -64,7 +64,7 @@ export default {
thClass: 'gl-w-40p',
},
{
- key: 'defaultValue',
+ key: 'default',
label: s__('CiCatalogComponent|Default Value'),
thClass: 'gl-w-40p',
},
@@ -103,7 +103,6 @@ export default {
data-testid="component-section"
>
<h3 class="gl-font-size-h2" data-testid="component-name">{{ component.name }}</h3>
- <p class="gl-mt-5">{{ component.description }}</p>
<div class="gl-display-flex">
<pre
class="gl-w-85p gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0 gl-border-r-none"
@@ -124,7 +123,7 @@ export default {
</div>
<div class="gl-mt-5">
<b class="gl-display-block gl-mb-4"> {{ $options.i18n.inputTitle }}</b>
- <gl-table-lite :items="component.inputs.nodes" :fields="$options.fields">
+ <gl-table-lite :items="component.inputs" :fields="$options.fields">
<template #cell(required)="{ item }">
{{ humanizeBoolean(item.required) }}
</template>
diff --git a/app/assets/javascripts/ci/catalog/constants.js b/app/assets/javascripts/ci/catalog/constants.js
index 9591880e0fa..34c0ac797c1 100644
--- a/app/assets/javascripts/ci/catalog/constants.js
+++ b/app/assets/javascripts/ci/catalog/constants.js
@@ -1,7 +1,5 @@
import { helpPagePath } from '~/helpers/help_page_helper';
-// We disable this for the entire file until the mock data is cleanup
-/* eslint-disable @gitlab/require-i18n-strings */
export const CATALOG_FEEDBACK_DISMISSED_KEY = 'catalog_feedback_dismissed';
export const SORT_OPTION_CREATED = 'CREATED';
@@ -9,35 +7,3 @@ export const SORT_ASC = 'ASC';
export const SORT_DESC = 'DESC';
export const COMPONENTS_DOCS_URL = helpPagePath('ci/components/index');
-
-export const componentsMockData = {
- __typename: 'CiComponentConnection',
- nodes: [
- {
- id: 'gid://gitlab/Ci::Component/1',
- name: 'Ruby gal',
- description: 'This is a pretty amazing component that does EVERYTHING ruby.',
- path: 'gitlab.com/gitlab-org/ruby-gal@~latest',
- inputs: { nodes: [{ name: 'version', defaultValue: '1.0.0', required: true }] },
- },
- {
- id: 'gid://gitlab/Ci::Component/2',
- name: 'Javascript madness',
- description: 'Adds some spice to your life.',
- path: 'gitlab.com/gitlab-org/javascript-madness@~latest',
- inputs: {
- nodes: [
- { name: 'isFun', defaultValue: 'true', required: true },
- { name: 'RandomNumber', defaultValue: '10', required: false },
- ],
- },
- },
- {
- id: 'gid://gitlab/Ci::Component/3',
- name: 'Go go go',
- description: 'When you write Go, you gotta go go go.',
- path: 'gitlab.com/gitlab-org/go-go-go@~latest',
- inputs: { nodes: [{ name: 'version', defaultValue: '1.0.0', required: true }] },
- },
- ],
-};
diff --git a/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_components.query.graphql b/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_components.query.graphql
index 92272bd97c5..41ac72aa9de 100644
--- a/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_components.query.graphql
+++ b/app/assets/javascripts/ci/catalog/graphql/queries/get_ci_catalog_resource_components.query.graphql
@@ -2,17 +2,17 @@ query getCiCatalogResourceComponents($fullPath: ID!) {
ciCatalogResource(fullPath: $fullPath) {
id
webPath
- components @client {
- nodes {
- id
- name
- description
- path
- inputs {
- nodes {
+ latestVersion {
+ id
+ components {
+ nodes {
+ id
+ name
+ path
+ inputs {
name
- defaultValue
required
+ default
}
}
}
diff --git a/app/assets/javascripts/ci/catalog/graphql/settings.js b/app/assets/javascripts/ci/catalog/graphql/settings.js
index c3c6dcca261..4038188a7ce 100644
--- a/app/assets/javascripts/ci/catalog/graphql/settings.js
+++ b/app/assets/javascripts/ci/catalog/graphql/settings.js
@@ -1,4 +1,3 @@
-import { componentsMockData } from '../constants';
import getCurrentPage from './queries/client/get_current_page.query.graphql';
export const ciCatalogResourcesItemsCount = 20;
@@ -40,9 +39,4 @@ export const resolvers = {
});
},
},
- CiCatalogResource: {
- components() {
- return componentsMockData;
- },
- },
};
diff --git a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
index db5f5b02d72..3d0e7db6a68 100644
--- a/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
+++ b/app/assets/javascripts/sidebar/components/reviewers/uncollapsed_reviewer_list.vue
@@ -131,8 +131,15 @@ export default {
}
},
reviewStateIcon(user) {
- if (user.mergeRequestInteraction.approved) return REVIEW_STATE_ICONS.APPROVED;
-
+ if (user.mergeRequestInteraction.approved) {
+ return {
+ ...REVIEW_STATE_ICONS.APPROVED,
+ class: [
+ REVIEW_STATE_ICONS.APPROVED.class,
+ this.loadingStates[user.id] === JUST_APPROVED && 'merge-request-approved-icon',
+ ],
+ };
+ }
return REVIEW_STATE_ICONS[user.mergeRequestInteraction.reviewState];
},
showRequestReviewButton(user) {
diff --git a/app/controllers/explore/catalog_controller.rb b/app/controllers/explore/catalog_controller.rb
index 4bcddf367b2..d384ad10c86 100644
--- a/app/controllers/explore/catalog_controller.rb
+++ b/app/controllers/explore/catalog_controller.rb
@@ -7,6 +7,9 @@ module Explore
feature_category :pipeline_composition
before_action :check_resource_access, only: :show
track_internal_event :index, name: 'unique_users_visiting_ci_catalog'
+ before_action do
+ push_frontend_feature_flag(:ci_catalog_components_tab, current_user)
+ end
def show; end
diff --git a/app/controllers/projects/gcp/artifact_registry/base_controller.rb b/app/controllers/projects/gcp/artifact_registry/base_controller.rb
new file mode 100644
index 00000000000..4084427f3e5
--- /dev/null
+++ b/app/controllers/projects/gcp/artifact_registry/base_controller.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Projects
+ module Gcp
+ module ArtifactRegistry
+ class BaseController < ::Projects::ApplicationController
+ before_action :ensure_feature_flag
+ before_action :ensure_saas
+ before_action :authorize_read_container_image!
+ before_action :ensure_private_project
+
+ feature_category :container_registry
+ urgency :low
+
+ private
+
+ def ensure_feature_flag
+ return if Feature.enabled?(:gcp_technical_demo, project)
+
+ @error = 'Feature flag disabled'
+
+ render
+ end
+
+ def ensure_saas
+ return if Gitlab.com_except_jh? # rubocop: disable Gitlab/AvoidGitlabInstanceChecks -- demo requirement
+
+ @error = "Can't run here"
+
+ render
+ end
+
+ def ensure_private_project
+ return if project.private?
+
+ @error = 'Can only run on private projects'
+
+ render
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb b/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb
new file mode 100644
index 00000000000..b88b86975a4
--- /dev/null
+++ b/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+module Projects
+ module Gcp
+ module ArtifactRegistry
+ class DockerImagesController < Projects::Gcp::ArtifactRegistry::BaseController
+ before_action :require_gcp_params
+ before_action :handle_pagination
+
+ REPO_NAME_REGEX = %r{/repositories/(.*)/dockerImages/}
+
+ def index
+ result = service.execute(page_token: params[:page_token])
+
+ if result.success?
+ @docker_images = process_docker_images(result.payload[:images] || [])
+ @next_page_token = result.payload[:next_page_token]
+ @artifact_repository_name = artifact_repository_name
+ @error = @docker_images.blank? ? 'No docker images' : false
+ else
+ @error = result.message
+ end
+ end
+
+ private
+
+ def service
+ ::Integrations::GoogleCloudPlatform::ArtifactRegistry::ListDockerImagesService.new(
+ project: @project,
+ current_user: current_user,
+ params: {
+ gcp_project_id: gcp_project_id,
+ gcp_location: gcp_location,
+ gcp_repository: gcp_ar_repository,
+ gcp_wlif: gcp_wlif_url
+ }
+ )
+ end
+
+ def process_docker_images(raw_images)
+ raw_images.map { |r| process_docker_image(r) }
+ end
+
+ def process_docker_image(raw_image)
+ DockerImage.new(
+ name: raw_image[:name],
+ uri: raw_image[:uri],
+ tags: raw_image[:tags],
+ image_size_bytes: raw_image[:size_bytes],
+ media_type: raw_image[:media_type],
+ upload_time: raw_image[:uploaded_at],
+ build_time: raw_image[:built_at],
+ update_time: raw_image[:updated_at]
+ )
+ end
+
+ def artifact_repository_name
+ return unless @docker_images.present?
+
+ (@docker_images.first.name || '')[REPO_NAME_REGEX, 1]
+ end
+
+ def handle_pagination
+ @page = Integer(params[:page] || 1)
+ @page_tokens = {}
+ @previous_page_token = nil
+
+ if params[:page_tokens]
+ @page_tokens = ::Gitlab::Json.parse(Base64.decode64(params[:page_tokens]))
+ @previous_page_token = @page_tokens[(@page - 1).to_s]
+ end
+
+ @page_tokens[@page.to_s] = params[:page_token]
+ @page_tokens = Base64.encode64(::Gitlab::Json.dump(@page_tokens.compact))
+ end
+
+ def require_gcp_params
+ return unless gcp_project_id.blank? || gcp_location.blank? || gcp_ar_repository.blank? || gcp_wlif_url.blank?
+
+ redirect_to new_namespace_project_gcp_artifact_registry_setup_path
+ end
+
+ def gcp_project_id
+ params[:gcp_project_id]
+ end
+
+ def gcp_location
+ params[:gcp_location]
+ end
+
+ def gcp_ar_repository
+ params[:gcp_ar_repository]
+ end
+
+ def gcp_wlif_url
+ params[:gcp_wlif_url]
+ end
+
+ class DockerImage
+ include ActiveModel::API
+
+ attr_accessor :name, :uri, :tags, :image_size_bytes, :upload_time, :media_type, :build_time, :update_time
+
+ SHORT_NAME_REGEX = %r{dockerImages/(.*)$}
+
+ def short_name
+ (name || '')[SHORT_NAME_REGEX, 1]
+ end
+
+ def updated_at
+ return unless update_time
+
+ Time.zone.parse(update_time)
+ end
+
+ def built_at
+ return unless build_time
+
+ Time.zone.parse(build_time)
+ end
+
+ def uploaded_at
+ return unless upload_time
+
+ Time.zone.parse(upload_time)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/gcp/artifact_registry/setup_controller.rb b/app/controllers/projects/gcp/artifact_registry/setup_controller.rb
new file mode 100644
index 00000000000..e90304ce593
--- /dev/null
+++ b/app/controllers/projects/gcp/artifact_registry/setup_controller.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Projects
+ module Gcp
+ module ArtifactRegistry
+ class SetupController < ::Projects::Gcp::ArtifactRegistry::BaseController
+ def new; end
+ end
+ end
+ end
+end
diff --git a/app/models/integrations/asana.rb b/app/models/integrations/asana.rb
index 39407acd6c9..07e8b5904cb 100644
--- a/app/models/integrations/asana.rb
+++ b/app/models/integrations/asana.rb
@@ -14,11 +14,13 @@ module Integrations
non_empty_password_title: -> { s_('ProjectService|Enter new API key') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current API key.') },
placeholder: '0/68a9e79b868c6789e79a124c30b0', # Example Personal Access Token from Asana docs
+ description: -> { s_('User API token. The user must have access to the task. All comments are attributed to this user.') },
required: true
field :restrict_to_branch,
title: -> { s_('Integrations|Restrict to branch (optional)') },
- help: -> { s_('AsanaService|Comma-separated list of branches to be automatically inspected. Leave blank to include all branches.') }
+ help: -> { s_('AsanaService|Comma-separated list of branches to be automatically inspected. Leave blank to include all branches.') },
+ description: -> { s_('Comma-separated list of branches to be automatically inspected. Leave blank to include all branches.') }
def self.title
'Asana'
diff --git a/app/models/integrations/assembla.rb b/app/models/integrations/assembla.rb
index bbdd0e183f2..c441ead50de 100644
--- a/app/models/integrations/assembla.rb
+++ b/app/models/integrations/assembla.rb
@@ -6,12 +6,14 @@ module Integrations
field :token,
type: :password,
+ description: -> { s_('The authentication token.') },
non_empty_password_title: -> { s_('ProjectService|Enter new token') },
non_empty_password_help: -> { s_('ProjectService|Leave blank to use your current token.') },
placeholder: '',
required: true
field :subdomain,
+ description: -> { s_('The subdomain setting.') },
exposes_secrets: true,
placeholder: ''
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 6b56fe0b75b..52158f81c4f 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -3,15 +3,14 @@
- emails_disabled = @group.emails_disabled?
.group-home-panel
- .gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-sm-flex-direction-column.gl-md-flex-direction-row.gl-gap-3.gl-my-5
+ .gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-flex-direction-column.gl-sm-flex-direction-row.gl-gap-3.gl-my-5
.home-panel-title-row.gl-display-flex
- = render Pajamas::AvatarComponent.new(@group, alt: @group.name, size: 48, class: 'float-none gl-flex-shrink-0 gl-mr-3', avatar_options: { itemprop: 'logo' })
- %div
- %h1.home-panel-title.gl-font-size-h-display.gl-mt-3.gl-mb-2.gl-ml-2.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-gap-3.gl-word-break-word{ itemprop: 'name' }
- = @group.name
- %span.visibility-icon.gl-text-secondary.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
- = visibility_level_icon(@group.visibility_level, options: { class: 'icon' })
- = render_if_exists 'shared/tier_badge', source: @group, namespace_to_track: @group
+ = render Pajamas::AvatarComponent.new(@group, alt: @group.name, size: 48, class: 'float-none gl-align-self-start gl-flex-shrink-0 gl-mr-3', avatar_options: { itemprop: 'logo' })
+ %h1.home-panel-title.gl-heading-1.gl-mt-3.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-gap-3.gl-word-break-word{ class: 'gl-mb-0!', itemprop: 'name' }
+ = @group.name
+ %span.visibility-icon.gl-text-secondary.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
+ = visibility_level_icon(@group.visibility_level, options: { class: 'icon' })
+ = render_if_exists 'shared/tier_badge', source: @group, namespace_to_track: @group
- if current_user
.home-panel-buttons.gl-display-flex.gl-justify-content-md-end.gl-align-items-center.gl-flex-wrap.gl-gap-3{ data: { testid: 'group-buttons' } }
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index ba590d523af..63226838166 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -5,18 +5,17 @@
%header.project-home-panel.js-show-on-project-root.gl-mt-5{ class: [("empty-project" if empty_repo)] }
.gl-display-flex.gl-justify-content-space-between.gl-flex-wrap.gl-flex-direction-column.gl-md-flex-direction-row.gl-gap-5
.home-panel-title-row.gl-display-flex.gl-align-items-center
- = render Pajamas::AvatarComponent.new(@project, alt: @project.name, class: 'gl-flex-shrink-0 gl-mr-3', size: 48, avatar_options: { itemprop: 'image' })
- .gl-ml-2
- %h1.home-panel-title.gl-font-size-h-display.gl-mt-3.gl-mb-2.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-gap-3.gl-word-break-word{ data: { testid: 'project-name-content' }, itemprop: 'name' }
- = @project.name
- = visibility_level_content(@project, css_class: 'visibility-icon gl-text-secondary', icon_css_class: 'icon')
- = render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: @project, additional_classes: 'gl-align-self-center'
- - if @project.catalog_resource
- = render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(@project.catalog_resource), css_class: 'gl-mx-0' }
- - if @project.group
- = render_if_exists 'shared/tier_badge', source: @project, namespace_to_track: @project.namespace
- .gl-text-secondary
- = render_if_exists "projects/home_mirror"
+ = render Pajamas::AvatarComponent.new(@project, alt: @project.name, class: 'gl-align-self-start gl-flex-shrink-0 gl-mr-3', size: 48, avatar_options: { itemprop: 'image' })
+ %h1.home-panel-title.gl-heading-1.gl-mt-3.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-gap-3.gl-word-break-word{ class: 'gl-mb-0!', data: { testid: 'project-name-content' }, itemprop: 'name' }
+ = @project.name
+ = visibility_level_content(@project, css_class: 'visibility-icon gl-display-inline-flex gl-text-secondary', icon_css_class: 'icon')
+ = render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: @project, additional_classes: 'gl-align-self-center'
+ - if @project.catalog_resource
+ = render partial: 'shared/ci_catalog_badge', locals: { href: explore_catalog_path(@project.catalog_resource), css_class: 'gl-mx-0' }
+ - if @project.group
+ = render_if_exists 'shared/tier_badge', source: @project, namespace_to_track: @project.namespace
+ .gl-text-secondary
+ = render_if_exists "projects/home_mirror"
.project-repo-buttons.gl-display-flex.gl-justify-content-md-end.gl-align-items-center.gl-flex-wrap.gl-gap-3
- if current_user
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml
new file mode 100644
index 00000000000..0118fe94810
--- /dev/null
+++ b/app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml
@@ -0,0 +1,33 @@
+.gl-display-flex.gl-flex-direction-column
+ .gl-display-flex.gl-flex-direction-column.gl-border-b-solid.gl-border-t-solid.gl-border-t-1.gl-border-b-1.gl-border-t-transparent.gl-border-b-gray-100
+ .gl-display-flex.gl-align-items-center.gl-py-3
+ .gl-display-flex.gl-flex-direction-column.gl-sm-flex-direction-row.gl-justify-content-space-between.gl-align-items-stretch.gl-flex-grow-1
+ .gl-display-flex.gl-flex-direction-column.gl-mb-3.gl-sm-mb-0.gl-min-w-0.gl-flex-grow-1
+ .gl-display-flex.gl-align-items-center.gl-text-body.gl-font-weight-bold.gl-font-size-h2
+ %span.gl-text-body.gl-font-weight-bold= docker_image.short_name
+ .gl-bg-gray-50.gl-inset-border-1-gray-100.gl-rounded-base.gl-pt-6
+ .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
+ = sprite_icon('information-o', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
+ Full name: #{docker_image.name}
+ .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
+ = sprite_icon('earth', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
+ URI:
+ %a{ href: docker_image.uri, target: 'blank', rel: 'noopener noreferrer' }= docker_image.uri
+ .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
+ = sprite_icon('doc-code', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
+ Media Type: #{docker_image.media_type}
+ .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
+ = sprite_icon('archive', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
+ Size: #{number_to_human_size(docker_image.image_size_bytes)}
+ .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
+ = sprite_icon('calendar', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
+ Built at: #{docker_image.built_at&.to_fs}
+ .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
+ = sprite_icon('calendar', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
+ Uploaded at: #{docker_image.uploaded_at&.to_fs}
+ .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
+ = sprite_icon('calendar', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
+ Updated at: #{docker_image.updated_at&.to_fs}
+ - if docker_image.tags.present?
+ .gl-display-flex.gl-align-items-center.gl-text-gray-500.gl-min-h-6.gl-min-w-0.gl-flex-grow-1.gl-pt-4
+ = render partial: 'docker_image_tag', collection: docker_image.tags
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml
new file mode 100644
index 00000000000..a030cd7d634
--- /dev/null
+++ b/app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml
@@ -0,0 +1 @@
+%a.gl-button.btn.btn-md.btn-default.gl-mr-3!= docker_image_tag
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml
new file mode 100644
index 00000000000..df98ba8d68e
--- /dev/null
+++ b/app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml
@@ -0,0 +1,13 @@
+.gl-display-flex.gl-justify-content-center
+ %nav.gl-pagination.gl-mt-3
+ .gl-keyset-pagination.btn-group
+ - if @page > 1
+ = link_to 'Prev', namespace_project_gcp_artifact_registry_docker_images_path(params[:namespace_id], params[:project_id], page_token: @previous_page_token, page_tokens: @page_tokens, page: @page - 1, gcp_project_id: params[:gcp_project_id], gcp_location: params[:gcp_location], gcp_ar_repository: params[:gcp_ar_repository], gcp_wlif_url: params[:gcp_wlif_url]), class: 'btn btn-default btn-md gl-button'
+ - else
+ %span.btn.btn-default.btn-md.gl-button.disabled= 'Prev'
+ - if @next_page_token.present?
+ = link_to 'Next', namespace_project_gcp_artifact_registry_docker_images_path(params[:namespace_id], params[:project_id], page_token: @next_page_token, page_tokens: @page_tokens, page: @page + 1, gcp_project_id: params[:gcp_project_id], gcp_location: params[:gcp_location], gcp_ar_repository: params[:gcp_ar_repository], gcp_wlif_url: params[:gcp_wlif_url]), class: 'btn btn-default btn-md gl-button'
+ - else
+ %span.btn.btn-default.btn-md.gl-button.disabled= 'Next'
+
+
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/index.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/index.html.haml
new file mode 100644
index 00000000000..b487a175691
--- /dev/null
+++ b/app/views/projects/gcp/artifact_registry/docker_images/index.html.haml
@@ -0,0 +1,23 @@
+- page_title 'Artifact Registry Docker Images'
+
+- unless @error
+ .gl-display-flex.gl-flex-direction-column
+ .gl-display-flex.gl-justify-content-space-between.gl-py-3
+ .gl-flex-direction-column.gl-flex-grow-1
+ .gl-display-flex
+ .gl-display-flex.gl-flex-direction-column
+ %h2.gl-font-size-h1.gl-mt-3.gl-mb-0 Docker Images of #{@artifact_repository_name}
+ = render partial: 'pagination'
+ = render partial: 'docker_image', collection: @docker_images
+ = render partial: 'pagination'
+- else
+ .flash-container.flash-container-page.sticky
+ .gl-alert.flash-notice.gl-alert-info
+ .gl-alert-icon-container
+ = sprite_icon('information-o', css_class: 's16 gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-content
+ .gl-alert-body
+ - if @error
+ = @error
+ - else
+ Nothing to show here.
diff --git a/app/views/projects/gcp/artifact_registry/setup/new.html.haml b/app/views/projects/gcp/artifact_registry/setup/new.html.haml
new file mode 100644
index 00000000000..39ce0093372
--- /dev/null
+++ b/app/views/projects/gcp/artifact_registry/setup/new.html.haml
@@ -0,0 +1,31 @@
+- page_title 'Artifact Registry Setup'
+
+- if @error.present?
+ .flash-container.flash-container-page.sticky
+ .gl-alert.flash-notice.gl-alert-info
+ .gl-alert-icon-container
+ = sprite_icon('information-o', css_class: 's16 gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-content
+ .gl-alert-body= @error
+- else
+ %p
+
+ = form_tag namespace_project_gcp_artifact_registry_docker_images_path , method: :get do
+ .form-group.row
+ = label_tag :gcp_project_id, 'Google Project ID', class: 'col-form-label col-md-2'
+ .col-md-4
+ = text_field_tag :gcp_project_id, nil, class: 'form-control gl-form-input gl-mr-3'
+ .form-group.row
+ = label_tag :gcp_location, 'Google Project Location', class: 'col-form-label col-md-2'
+ .col-md-4
+ = text_field_tag :gcp_location, nil, class: 'form-control gl-form-input gl-mr-3'
+ .form-group.row
+ = label_tag :gcp_ar_repository, 'Artifact Registry Repository Name', class: 'col-form-label col-md-2'
+ .col-md-4
+ = text_field_tag :gcp_ar_repository, nil, class: 'form-control gl-form-input gl-mr-3'
+ .form-group.row
+ = label_tag :gcp_wlif_url, 'Worflow Identity Federation url', class: 'col-form-label col-md-2'
+ .col-md-4
+ = text_field_tag :gcp_wlif_url, nil, class: 'form-control gl-form-input gl-mr-3'
+ .form-actions
+ = submit_tag 'Setup', class: 'gl-button btn btn-confirm'