diff --git a/app/assets/javascripts/clusters.js b/app/assets/javascripts/clusters.js index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..63ff676486b988e76e5cbcccb434a12e5a7fe216 100644 --- a/app/assets/javascripts/clusters.js +++ b/app/assets/javascripts/clusters.js @@ -0,0 +1,136 @@ +/* globals Flash */ +import Visibility from 'visibilityjs'; +import axios from 'axios'; +import Poll from './lib/utils/poll'; +import { s__ } from './locale'; +import './flash'; + +class ClusterService { + constructor(options = {}) { + this.options = options; + } + + fetchData() { + return axios.get(this.options.endpoints.checkStatus); + } + + updateData(value) { + return axios.put(this.options.endpoints.editPath, + { + cluster: { + enabled: value, + }, + }, + ); + } +} +/** + * Handles visibily toggle + * Polls the state + */ +export default class ClusterEdit { + constructor() { + const dataset = document.querySelector('.js-edit-cluster-form').dataset; + + this.state = { + endpoints: { + checkStatus: dataset.checkStatus, + editPath: dataset.editPath, + }, + canUpdate: dataset.canUpdate, + clusterStatus: dataset.clusterStatus, + }; + + this.service = new ClusterService({ endpoints: this.state.endpoints }); + this.toggleButton = document.querySelector('.js-toggle-cluster'); + this.errorContainer = document.querySelector('.js-cluster-error'); + this.successContainer = document.querySelector('.js-cluster-success'); + this.creatingContainer = document.querySelector('.js-cluster-creating'); + this.bindEvents(); + } + + bindEvents() { + if (!this.canUpdate) { + this.disableToggle(); + } + + if (this.clusterStatus) { + // update to enable or disabled! + } + + this.toggleButton.addEventListener('click', this.toggle.bind(this)); + + document.querySelector('.js-edit-cluster-button').addEventListener('click', this.updateData.bind(this)); + + this.initPoling(); + } + + toggle() { + this.toggleButton.classList.toggle('checked'); + this.toggleStatus = this.toggleButton.classList.contains('checked').toString(); + } + + updateData() { + this.service.updateData(this.state.toggleStatus); + } + + disableToggle(disable = true) { + this.toggleButton.classList.toggle('disabled', disable); + this.toggleButton.setAttribute('disabled', disable); + } + + initPoling() { + if (this.state.clusterStatus === 'created') return; + + this.poll = new Poll({ + resource: this.service, + method: 'fetchData', + successCallback: (data) => { + const { status } = data.data; + this.updateContainer(status); + }, + errorCallback: () => { + this.updateContainer('error'); + Flash(s__('ClusterIntegration|Something went wrong on our end.')); + }, + }); + + if (!Visibility.hidden()) { + this.poll.makeRequest(); + } else { + this.service.fetchData(); + } + + Visibility.change(() => { + if (!Visibility.hidden()) { + this.poll.restart(); + } else { + this.poll.stop(); + } + }); + } + + hideAll() { + this.errorContainer.classList.add('hidden'); + this.successContainer.classList.add('hidden'); + this.creatingContainer.classList.add('hidden'); + } + + updateContainer(status) { + this.hideAll(); + switch (status) { + case 'created': + this.successContainer.classList.remove('hidden'); + break; + case 'error': + this.errorContainer.classList.remove('hidden'); + break; + case 'creating': + this.creatingContainer.classList.add('hidden'); + break; + default: + this.hideAll(); + } + } + +} diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 31214818496fc5c82eec15d94737c5572a650f03..7131889a0a0819cef89a12a2988d362382f31e3a 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -524,6 +524,11 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; case 'admin:impersonation_tokens:index': new gl.DueDateSelectors(); break; + case 'projects:clusters:edit': + import(/* webpackChunkName: "clusters" */ './clusters') + .then(cluster => new cluster.default()) // eslint-disable-line new-cap + .catch(() => {}); + break; } switch (path[0]) { case 'sessions': diff --git a/app/views/projects/clusters/edit.html.haml b/app/views/projects/clusters/edit.html.haml index 16d864423f557e5d49193ac73a246becfa952b27..0f6be36e56a0d62c5dcbc739acb271b502106835 100644 --- a/app/views/projects/clusters/edit.html.haml +++ b/app/views/projects/clusters/edit.html.haml @@ -1,4 +1,7 @@ -.row.prepend-top-default.edit-cluster-form.js-edit-cluster-form{ data: { check_status: status_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster.id), edit_path: namespace_project_cluster_path(@project.namespace, @project, @cluster.id)}} +.row.prepend-top-default.edit-cluster-form.js-edit-cluster-form{ data: { check_status: status_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster.id, format: :json), + edit_path: namespace_project_cluster_path(@project.namespace, @project, @cluster.id, format: :json), + can_update: 'true', + cluster_status: '' }} = render 'sidebar' .col-lg-8 %h4.prepend-top-0 @@ -16,21 +19,37 @@ = s_('ClusterIntegration|Cluster integration is disabled for this project.') %label.toggle-wrapper - %button{ type: 'button', class: 'js-toggle-cluster project-feature-toggle', aria: { label: 'Toggle' }, data: { 'enabled-text': 'Enable', 'disabled-text': 'disabled' } } + %button{ type: 'button', class: 'js-toggle-cluster project-feature-toggle', 'aria-label': 'Toggle', data: { 'enabled-text': 'Enabled', 'disabled-text': 'Disabled' } } + -# if can?(current_user, :update_cluster, @cluster) .form-group + %button{ type: 'button', class: 'js-edit-cluster-button btn btn-success'} + = s_('ClusterIntegration|Save changes') - -# render errors TODO - #= form_errors(@cluster) - -# if can?(current_user, :update_cluster, @cluster) .form_group %label = s_('ClusterIntegration|Google container engine') - %p + - link_gke = link_to(s_('ClusterIntegration|Manage your cluster on GKE'), '', target: '_blank', rel: 'noopener noreferrer') - = s_('ClusterIntegration|This cluster was not set up on Google Container Engine. %{link_gke}').html_safe % { link_gke: link_gke } + .hidden.js-cluster-error + %p + = s_('ClusterIntegration|This cluster was set up on Google Container Engine. %{link_gke}').html_safe % { link_gke: link_gke } + .alert.alert-info{ role: 'alert' } + = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine.') + + .hidden.js-cluster-success + %p + = s_('ClusterIntegration|This cluster was set up on Google Container Engine. %{link_gke}').html_safe % { link_gke: link_gke } + .alert.alert-info{ role: 'alert' } + = s_('ClusterIntegration|Cluster was successfully created on Google Container Engine.') + + .hidden.js-cluster-creating + %p + = s_('ClusterIntegration|This cluster was set up on Google Container Engine. %{link_gke}').html_safe % { link_gke: link_gke } + .alert.alert-info{ role: 'alert' } + = s_('ClusterIntegration|Cluster is being created on Google Container Engine...') .form_group %label @@ -50,18 +69,8 @@ = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger') + %br = link_to "Enable", namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :put %br = link_to "Disable", namespace_project_cluster_path(@project.namespace, @project, @cluster.id, cluster: {enabled: 'false'}), method: :put - -%br --# status GET --# status: The current status of the operation. --# status_reason: If an error has occurred, a textual description of the error. -= link_to 'Check status', status_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster.id), :remote => true -%br --# simply rendering error, if it happened -status_reason -%p= @cluster.status_reason --# Even if we got an error during the creation process, we don't delete cluster objects automatically, because we don't have a method to delete the cluster on gke. So users move to edit page from new page **regardless of validation errors**, and they have to delete the record manually. This is for giving users headsup that a new cluster might remain in gke. /cc @ayufan