diff options
author | Kamil Trzciński <ayufan@ayufan.eu> | 2018-11-09 14:59:56 +0000 |
---|---|---|
committer | Kamil Trzciński <ayufan@ayufan.eu> | 2018-11-09 14:59:56 +0000 |
commit | 8b1212ed6d9869d93b33a85149eef19a69c6842f (patch) | |
tree | e81b428e006a3e06b66affd658e75f418581875d /app | |
parent | aa958abae01a0744f398b7b4691515d636393af7 (diff) | |
parent | b054694e497d58302f9cbfd134e84f5304b68e27 (diff) | |
download | gitlab-ce-8b1212ed6d9869d93b33a85149eef19a69c6842f.tar.gz |
Merge branch '34758-group-cluster-controller' into 'master'
User can create a group level cluster and install applications
See merge request gitlab-org/gitlab-ce!22450
Diffstat (limited to 'app')
36 files changed, 306 insertions, 89 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 02dfe1c7d6f..71fc2ac7d80 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -9,7 +9,7 @@ import eventHub from './event_hub'; import { APPLICATION_STATUS, REQUEST_LOADING, REQUEST_SUCCESS, REQUEST_FAILURE } from './constants'; import ClustersService from './services/clusters_service'; import ClustersStore from './stores/clusters_store'; -import applications from './components/applications.vue'; +import Applications from './components/applications.vue'; import setupToggleButtons from '../toggle_buttons'; /** @@ -31,6 +31,7 @@ export default class Clusters { installKnativePath, installPrometheusPath, managePrometheusPath, + clusterType, clusterStatus, clusterStatusReason, helpPath, @@ -67,7 +68,7 @@ export default class Clusters { initDismissableCallout('.js-cluster-security-warning'); initSettingsPanels(); setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area')); - this.initApplications(); + this.initApplications(clusterType); if (this.store.state.status !== 'created') { this.updateContainer(null, this.store.state.status, this.store.state.statusReason); @@ -79,23 +80,21 @@ export default class Clusters { } } - initApplications() { + initApplications(type) { const { store } = this; const el = document.querySelector('#js-cluster-applications'); this.applications = new Vue({ el, - components: { - applications, - }, data() { return { state: store.state, }; }, render(createElement) { - return createElement('applications', { + return createElement(Applications, { props: { + type, applications: this.state.applications, helpPath: this.state.helpPath, ingressHelpPath: this.state.ingressHelpPath, diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js deleted file mode 100644 index 789c8360124..00000000000 --- a/app/assets/javascripts/clusters/clusters_index.js +++ /dev/null @@ -1,24 +0,0 @@ -import createFlash from '~/flash'; -import { __ } from '~/locale'; -import setupToggleButtons from '~/toggle_buttons'; -import initDismissableCallout from '~/dismissable_callout'; - -import ClustersService from './services/clusters_service'; - -export default () => { - const clusterList = document.querySelector('.js-clusters-list'); - - initDismissableCallout('.gcp-signup-offer'); - - // The empty state won't have a clusterList - if (clusterList) { - setupToggleButtons(document.querySelector('.js-clusters-list'), (value, toggle) => - ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } }).catch( - err => { - createFlash(__('Something went wrong on our end.')); - throw err; - }, - ), - ); - } -}; diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index c7ffb470d4d..c1026d1273a 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -13,7 +13,7 @@ import prometheusLogo from 'images/cluster_app_logos/prometheus.png'; import { s__, sprintf } from '../../locale'; import applicationRow from './application_row.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; -import { APPLICATION_STATUS, INGRESS } from '../constants'; +import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants'; export default { components: { @@ -21,6 +21,11 @@ export default { clipboardButton, }, props: { + type: { + type: String, + required: false, + default: CLUSTER_TYPE.PROJECT, + }, applications: { type: Object, required: false, @@ -59,6 +64,9 @@ export default { prometheusLogo, }), computed: { + isProjectCluster() { + return this.type === CLUSTER_TYPE.PROJECT; + }, helmInstalled() { return ( this.applications.helm.status === APPLICATION_STATUS.INSTALLED || @@ -281,6 +289,7 @@ export default { </div> </application-row> <application-row + v-if="isProjectCluster" id="prometheus" :logo-url="prometheusLogo" :title="applications.prometheus.title" @@ -299,6 +308,7 @@ export default { </div> </application-row> <application-row + v-if="isProjectCluster" id="runner" :logo-url="gitlabLogo" :title="applications.runner.title" @@ -317,6 +327,7 @@ export default { </div> </application-row> <application-row + v-if="isProjectCluster" id="jupyter" :logo-url="jupyterhubLogo" :title="applications.jupyter.title" diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index d707420c845..15cf4a56138 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -1,3 +1,10 @@ +// These need to match the enum found in app/models/clusters/cluster.rb +export const CLUSTER_TYPE = { + INSTANCE: 'instance_type', + GROUP: 'group_type', + PROJECT: 'project_type', +}; + // These need to match what is returned from the server export const APPLICATION_STATUS = { NOT_INSTALLABLE: 'not_installable', diff --git a/app/assets/javascripts/pages/groups/clusters/destroy/index.js b/app/assets/javascripts/pages/groups/clusters/destroy/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/destroy/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/groups/clusters/index/index.js b/app/assets/javascripts/pages/groups/clusters/index/index.js new file mode 100644 index 00000000000..845a5f7042c --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/index/index.js @@ -0,0 +1,5 @@ +import initDismissableCallout from '~/dismissable_callout'; + +document.addEventListener('DOMContentLoaded', () => { + initDismissableCallout('.gcp-signup-offer'); +}); diff --git a/app/assets/javascripts/pages/groups/clusters/show/index.js b/app/assets/javascripts/pages/groups/clusters/show/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/show/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/groups/clusters/update/index.js b/app/assets/javascripts/pages/groups/clusters/update/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/update/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/groups/index.js b/app/assets/javascripts/pages/groups/index.js new file mode 100644 index 00000000000..bf80d8b8193 --- /dev/null +++ b/app/assets/javascripts/pages/groups/index.js @@ -0,0 +1,16 @@ +import initDismissableCallout from '~/dismissable_callout'; +import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; + +document.addEventListener('DOMContentLoaded', () => { + const { page } = document.body.dataset; + const newClusterViews = [ + 'groups:clusters:new', + 'groups:clusters:create_gcp', + 'groups:clusters:create_user', + ]; + + if (newClusterViews.indexOf(page) > -1) { + initDismissableCallout('.gcp-signup-offer'); + initGkeDropdowns(); + } +}); diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js index e4b8baede58..845a5f7042c 100644 --- a/app/assets/javascripts/pages/projects/clusters/index/index.js +++ b/app/assets/javascripts/pages/projects/clusters/index/index.js @@ -1,5 +1,5 @@ -import ClustersIndex from '~/clusters/clusters_index'; +import initDismissableCallout from '~/dismissable_callout'; document.addEventListener('DOMContentLoaded', () => { - new ClustersIndex(); // eslint-disable-line no-new + initDismissableCallout('.gcp-signup-offer'); }); diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 71a3fd544f2..ad12cd101b6 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -25,6 +25,12 @@ .cluster-application-row { border-bottom: 1px solid $border-color; padding: $gl-padding; + + &:last-child { + border-bottom: 0; + border-bottom-left-radius: calc(#{$border-radius-default} - 1px); + border-bottom-right-radius: calc(#{$border-radius-default} - 1px); + } } } @@ -73,6 +79,10 @@ padding: $gl-padding-top $gl-padding; } + .card { + margin-bottom: $gl-vert-padding; + } + .empty-state .svg-content img { width: 145px; } @@ -80,6 +90,31 @@ .top-area .nav-controls > .btn.btn-add-cluster { margin-right: 0; } + + .clusters-table { + background-color: $gray-light; + padding: $gl-padding-8; + } + + .badge-light { + background-color: $white-normal; + } + + .gl-responsive-table-row { + padding: $gl-padding; + border: 0; + + &.table-row-header { + background-color: none; + border: 0; + font-weight: bold; + color: $gl-gray-500; + } + } +} + +.cluster-warning { + @include alert-variant(theme-color-level('warning', $alert-bg-level), theme-color-level('warning', $alert-border-level), theme-color-level('warning', $alert-color-level)); } .gcp-signup-offer { diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index f6f2060ebb5..2e9c77ae55c 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -183,13 +183,13 @@ class Clusters::ClustersController < Clusters::BaseController def gcp_cluster @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster| cluster.build_provider_gcp - end + end.present(current_user: current_user) end def user_cluster @user_cluster = ::Clusters::Cluster.new.tap do |cluster| cluster.build_platform_kubernetes - end + end.present(current_user: current_user) end def validate_gcp_token diff --git a/app/controllers/groups/clusters/applications_controller.rb b/app/controllers/groups/clusters/applications_controller.rb new file mode 100644 index 00000000000..8dd8a01cf40 --- /dev/null +++ b/app/controllers/groups/clusters/applications_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Groups::Clusters::ApplicationsController < Clusters::ApplicationsController + include ControllerWithCrossProjectAccessCheck + + prepend_before_action :group + requires_cross_project_access + + private + + def clusterable + @clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user) + end + + def group + @group ||= find_routable!(Group, params[:group_id] || params[:id]) + end +end diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb new file mode 100644 index 00000000000..50c44b7a58b --- /dev/null +++ b/app/controllers/groups/clusters_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Groups::ClustersController < Clusters::ClustersController + include ControllerWithCrossProjectAccessCheck + + prepend_before_action :check_group_clusters_feature_flag! + prepend_before_action :group + requires_cross_project_access + + layout 'group' + + private + + def clusterable + @clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user) + end + + def group + @group ||= find_routable!(Group, params[:group_id] || params[:id]) + end + + def check_group_clusters_feature_flag! + render_404 unless Feature.enabled?(:group_clusters) + end +end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 0c313e9e6d3..e9b9b9b7721 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -140,6 +140,10 @@ module GroupsHelper can?(current_user, "read_group_#{resource}".to_sym, @group) end + if can?(current_user, :read_cluster, @group) && Feature.enabled?(:group_clusters) + links << :kubernetes + end + if can?(current_user, :admin_group, @group) links << :settings end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 48d6c0daa0f..0ba056e57d4 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -29,7 +29,7 @@ module Clusters # we force autosave to happen when we save `Cluster` model has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true - has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', autosave: true + has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', inverse_of: :cluster, autosave: true has_one :application_helm, class_name: 'Clusters::Applications::Helm' has_one :application_ingress, class_name: 'Clusters::Applications::Ingress' @@ -144,6 +144,10 @@ module Clusters ) end + def allow_user_defined_namespace? + project_type? + end + private def restrict_modification diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index d69038be532..ea02ae6c9d8 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -38,6 +38,8 @@ module Clusters validates :namespace, exclusion: { in: RESERVED_NAMESPACES } + validate :no_namespace, unless: :allow_user_defined_namespace? + # We expect to be `active?` only when enabled and cluster is created (the api_url is assigned) validates :api_url, url: true, presence: true validates :token, presence: true @@ -52,6 +54,7 @@ module Clusters delegate :project, to: :cluster, allow_nil: true delegate :enabled?, to: :cluster, allow_nil: true delegate :managed?, to: :cluster, allow_nil: true + delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true delegate :kubernetes_namespace, to: :cluster alias_method :active?, :enabled? @@ -150,7 +153,8 @@ module Clusters end def build_kube_client! - raise "Incomplete settings" unless api_url && actual_namespace + raise "Incomplete settings" unless api_url + raise "No namespace" if cluster.project_type? && actual_namespace.empty? # can probably remove this line once we remove #actual_namespace unless (username && password) || token raise "Either username/password or token is required to access API" @@ -207,6 +211,12 @@ module Clusters self.token = self.token&.strip end + def no_namespace + if namespace + errors.add(:namespace, 'only allowed for project cluster') + end + end + def prevent_modification return unless managed? diff --git a/app/policies/clusters/cluster_policy.rb b/app/policies/clusters/cluster_policy.rb index 147943a3d6c..d6d590687e2 100644 --- a/app/policies/clusters/cluster_policy.rb +++ b/app/policies/clusters/cluster_policy.rb @@ -4,11 +4,7 @@ module Clusters class ClusterPolicy < BasePolicy alias_method :cluster, :subject + delegate { cluster.group } delegate { cluster.project } - - rule { can?(:maintainer_access) }.policy do - enable :update_cluster - enable :admin_cluster - end end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 73c93b22c95..6b4e56ef5e4 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -65,6 +65,10 @@ class GroupPolicy < BasePolicy enable :create_projects enable :admin_pipeline enable :admin_build + enable :read_cluster + enable :create_cluster + enable :update_cluster + enable :admin_cluster end rule { owner }.policy do diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a76a083bceb..1c082945299 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -258,6 +258,8 @@ class ProjectPolicy < BasePolicy enable :update_pages enable :read_cluster enable :create_cluster + enable :update_cluster + enable :admin_cluster enable :create_environment_terminal end diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index cff0e74d6ea..9cc137aa3bd 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -43,4 +43,16 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated def cluster_path(cluster, params = {}) raise NotImplementedError end + + def empty_state_help_text + nil + end + + def sidebar_text + raise NotImplementedError + end + + def learn_more_link + raise NotImplementedError + end end diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb index 78d632eb77c..7e6eccb648c 100644 --- a/app/presenters/clusters/cluster_presenter.rb +++ b/app/presenters/clusters/cluster_presenter.rb @@ -15,6 +15,8 @@ module Clusters def show_path if cluster.project_type? project_cluster_path(project, cluster) + elsif cluster.group_type? + group_cluster_path(group, cluster) else raise NotImplementedError end diff --git a/app/presenters/group_clusterable_presenter.rb b/app/presenters/group_clusterable_presenter.rb new file mode 100644 index 00000000000..d963c188559 --- /dev/null +++ b/app/presenters/group_clusterable_presenter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class GroupClusterablePresenter < ClusterablePresenter + extend ::Gitlab::Utils::Override + include ActionView::Helpers::UrlHelper + + override :cluster_status_cluster_path + def cluster_status_cluster_path(cluster, params = {}) + cluster_status_group_cluster_path(clusterable, cluster, params) + end + + override :install_applications_cluster_path + def install_applications_cluster_path(cluster, application) + install_applications_group_cluster_path(clusterable, cluster, application) + end + + override :cluster_path + def cluster_path(cluster, params = {}) + group_cluster_path(clusterable, cluster, params) + end + + override :empty_state_help_text + def empty_state_help_text + s_('ClusterIntegration|Adding an integration to your group will share the cluster across all your projects.') + end + + override :sidebar_text + def sidebar_text + s_('ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster.') + end + + override :learn_more_link + def learn_more_link + link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + end +end diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb index 12077b2e735..63e69b91b11 100644 --- a/app/presenters/project_clusterable_presenter.rb +++ b/app/presenters/project_clusterable_presenter.rb @@ -1,15 +1,31 @@ # frozen_string_literal: true class ProjectClusterablePresenter < ClusterablePresenter + extend ::Gitlab::Utils::Override + include ActionView::Helpers::UrlHelper + + override :cluster_status_cluster_path def cluster_status_cluster_path(cluster, params = {}) cluster_status_project_cluster_path(clusterable, cluster, params) end + override :install_applications_cluster_path def install_applications_cluster_path(cluster, application) install_applications_project_cluster_path(clusterable, cluster, application) end + override :cluster_path def cluster_path(cluster, params = {}) project_cluster_path(clusterable, cluster, params) end + + override :sidebar_text + def sidebar_text + s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.') + end + + override :learn_more_link + def learn_more_link + link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + end end diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb index c348cad4803..844807c2581 100644 --- a/app/services/clusters/applications/create_service.rb +++ b/app/services/clusters/applications/create_service.rb @@ -42,7 +42,16 @@ module Clusters def builders { "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm }, - "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress }, + "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress } + }.tap do |hash| + hash.merge!(project_builders) if cluster.project_type? + end + end + + # These applications will need extra configuration to enable them to work + # with groups of projects + def project_builders + { "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus }, "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner }, "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter }, diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 270db4a52fd..5a9da053780 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -36,6 +36,10 @@ module Clusters case clusterable when ::Project { cluster_type: :project_type, projects: [clusterable] } + when ::Group + { cluster_type: :group_type, groups: [clusterable] } + else + raise NotImplementedError end end diff --git a/app/views/clusters/clusters/_buttons.html.haml b/app/views/clusters/clusters/_buttons.html.haml new file mode 100644 index 00000000000..db2e247e341 --- /dev/null +++ b/app/views/clusters/clusters/_buttons.html.haml @@ -0,0 +1,4 @@ +-# This partial is overridden in EE +.nav-controls + %span.btn.btn-add-cluster.disabled.js-add-cluster + = s_("ClusterIntegration|Add Kubernetes cluster") diff --git a/app/views/clusters/clusters/_cluster.html.haml b/app/views/clusters/clusters/_cluster.html.haml index facbcb7fc59..adeca013749 100644 --- a/app/views/clusters/clusters/_cluster.html.haml +++ b/app/views/clusters/clusters/_cluster.html.haml @@ -1,24 +1,16 @@ -.gl-responsive-table-row - .table-section.section-30 - .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster") - .table-mobile-content - = link_to cluster.name, cluster.show_path - .table-section.section-30 - .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope") - .table-mobile-content= cluster.environment_scope - .table-section.section-30 - .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace") - .table-mobile-content= cluster.platform_kubernetes&.actual_namespace - .table-section.section-10 - .table-mobile-header{ role: "rowheader" } - .table-mobile-content - %button.js-project-feature-toggle.project-feature-toggle{ type: "button", - class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}", - "aria-label": s_("ClusterIntegration|Toggle Kubernetes Cluster"), - disabled: !cluster.can_toggle_cluster?, - data: { endpoint: clusterable.cluster_path(cluster, format: :json) } } - %input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? } - = icon("spinner spin", class: "loading-icon") - %span.toggle-icon - = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') - = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') +.card + .card-body.gl-responsive-table-row + .table-section.section-60 + .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster") + .table-mobile-content + = link_to cluster.name, cluster.show_path + - unless cluster.enabled? + %span.badge.badge-danger Connection disabled + .table-section.section-25 + .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope") + .table-mobile-content= cluster.environment_scope + .table-section.section-15.text-right + .table-mobile-header{ role: "rowheader" } + .table-mobile-content + %span.badge.badge-light + = cluster.project_type? ? s_("ClusterIntegration|Project cluster") : s_("ClusterIntegration|Group cluster") diff --git a/app/views/clusters/clusters/_empty_state.html.haml b/app/views/clusters/clusters/_empty_state.html.haml index 800e76d92ef..c926ec258f0 100644 --- a/app/views/clusters/clusters/_empty_state.html.haml +++ b/app/views/clusters/clusters/_empty_state.html.haml @@ -4,8 +4,10 @@ .col-12 .text-content %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation') - - link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - %p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page} + %p + = s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way.') + = clusterable.empty_state_help_text + = clusterable.learn_more_link - if clusterable.can_create_cluster? .text-center diff --git a/app/views/clusters/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml index 3d10348212f..6e4415c21a9 100644 --- a/app/views/clusters/clusters/_sidebar.html.haml +++ b/app/views/clusters/clusters/_sidebar.html.haml @@ -1,9 +1,6 @@ -- clusters_help_url = help_page_path('user/project/clusters/index.md') -- help_link_start = "<a href=\"%{url}\" target=\"_blank\" rel=\"noopener noreferrer\">".html_safe -- help_link_end = '</a>'.html_safe %h4.prepend-top-0 - = s_('ClusterIntegration|Kubernetes cluster integration') + = s_('ClusterIntegration|Add a Kubernetes cluster integration') %p - = s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.') + = clusterable.sidebar_text %p - = s_('ClusterIntegration|Learn more about %{help_link_start}Kubernetes%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: clusters_help_url }, help_link_end: help_link_end } + = clusterable.learn_more_link diff --git a/app/views/clusters/clusters/gcp/_show.html.haml b/app/views/clusters/clusters/gcp/_show.html.haml index ca55ccb8fdf..e9f05eaf453 100644 --- a/app/views/clusters/clusters/gcp/_show.html.haml +++ b/app/views/clusters/clusters/gcp/_show.html.haml @@ -33,9 +33,10 @@ = s_('ClusterIntegration|Show') = clipboard_button(text: @cluster.platform_kubernetes.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default') - .form-group - = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') - = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + - if @cluster.allow_user_defined_namespace? + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') .form-group .form-check diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml index a55de84b5cd..ad6d1d856d6 100644 --- a/app/views/clusters/clusters/index.html.haml +++ b/app/views/clusters/clusters/index.html.haml @@ -10,14 +10,13 @@ .top-area.adjust .nav-text = s_("ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project") - .ci-table.js-clusters-list + = render 'clusters/clusters/buttons' + .clusters-table.js-clusters-list .gl-responsive-table-row.table-row-header{ role: "row" } - .table-section.section-30{ role: "rowheader" } + .table-section.section-60{ role: "rowheader" } = s_("ClusterIntegration|Kubernetes cluster") .table-section.section-30{ role: "rowheader" } = s_("ClusterIntegration|Environment scope") - .table-section.section-30{ role: "rowheader" } - = s_("ClusterIntegration|Project namespace") .table-section.section-10{ role: "rowheader" } - @clusters.each do |cluster| = render "cluster", cluster: cluster.present(current_user: current_user) diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml index 7ea85fe43d6..8a7f7a5c978 100644 --- a/app/views/clusters/clusters/show.html.haml +++ b/app/views/clusters/clusters/show.html.haml @@ -15,6 +15,7 @@ install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter), install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative), toggle_status: @cluster.enabled? ? 'true': 'false', + cluster_type: @cluster.cluster_type, cluster_status: @cluster.status_name, cluster_status_reason: @cluster.status_reason, help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml index e4758938059..9793c77fc2b 100644 --- a/app/views/clusters/clusters/user/_form.html.haml +++ b/app/views/clusters/clusters/user/_form.html.haml @@ -21,9 +21,10 @@ = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token'), class: 'label-bold' = platform_kubernetes_field.text_field :token, class: 'form-control', placeholder: s_('ClusterIntegration|Service token'), autocomplete: 'off' - .form-group - = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' - = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + - if @user_cluster.allow_user_defined_namespace? + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') .form-group .form-check diff --git a/app/views/clusters/clusters/user/_show.html.haml b/app/views/clusters/clusters/user/_show.html.haml index ad8c35e32e3..cac8e72edd3 100644 --- a/app/views/clusters/clusters/user/_show.html.haml +++ b/app/views/clusters/clusters/user/_show.html.haml @@ -22,9 +22,10 @@ %button.js-show-cluster-token.btn-blank{ type: 'button' } = s_('ClusterIntegration|Show') - .form-group - = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' - = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + - if @cluster.allow_user_defined_namespace? + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') .form-group .form-check diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index b390c396a09..3cd5168c1f7 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -116,6 +116,19 @@ %strong.fly-out-top-item-name = _('Members') + - if group_sidebar_link?(:kubernetes) + = nav_link(controller: [:clusters]) do + = link_to group_clusters_path(@group) do + .nav-icon-container + = sprite_icon('cloud-gear') + %span.nav-item-name + = _('Kubernetes') + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(controller: [:clusters], html_options: { class: "fly-out-top-item" } ) do + = link_to group_clusters_path(@group), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do + %strong.fly-out-top-item-name + = _('Kubernetes') + - if group_sidebar_link?(:settings) = nav_link(path: group_nav_link_paths) do = link_to edit_group_path(@group) do |