Commit 7db2ef36 authored by Dennis Tang's avatar Dennis Tang

fetch gke parameters from frontend

parent a5f43b3d
/* global gapi */
import Vue from 'vue';
import Flash from '~/flash';
import { s__ } from '~/locale';
import GkeProjectIdDropdown from '~/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue';
import GkeZoneDropdown from '~/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue';
import GkeMachineTypeDropdown from '~/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue';
const GCP_API_ERROR =
'ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later.';
function mountGkeProjectIdDropdown() {
const el = document.getElementById('js-gcp-project-id-dropdown-entry-point');
const hiddenInput = el.querySelector('input');
if (!el) return;
// debugger;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
GkeProjectIdDropdown,
},
render: createElement =>
createElement('gke-project-id-dropdown', {
props: {
docsUrl: el.dataset.docsurl,
service: gapi.client.cloudresourcemanager,
fieldName: hiddenInput.getAttribute('name'),
fieldId: hiddenInput.getAttribute('id'),
},
}),
});
}
function mountGkeZoneDropdown() {
const el = document.getElementById('js-gcp-zone-dropdown-entry-point');
const hiddenInput = el.querySelector('input');
if (!el) return;
// debugger;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
GkeZoneDropdown,
},
render: createElement =>
createElement('gke-zone-dropdown', {
props: {
service: gapi.client.compute,
fieldName: hiddenInput.getAttribute('name'),
fieldId: hiddenInput.getAttribute('id'),
},
}),
});
}
function mountGkeMachineTypeDropdown() {
const el = document.getElementById('js-gcp-machine-type-dropdown-entry-point');
const hiddenInput = el.querySelector('input');
if (!el) return;
// debugger;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
GkeMachineTypeDropdown,
},
render: createElement =>
createElement('gke-machine-type-dropdown', {
props: {
service: gapi.client.compute,
fieldName: hiddenInput.getAttribute('name'),
fieldId: hiddenInput.getAttribute('id'),
},
}),
});
}
function initializeGapiClient() {
const el = document.getElementById('new_cluster');
gapi.client.setToken({ access_token: el.dataset.token });
gapi.client
.load('https://www.googleapis.com/discovery/v1/apis/cloudresourcemanager/v1/rest')
.then(() => {
mountGkeProjectIdDropdown();
})
.catch(() => {
Flash(s__(GCP_API_ERROR));
});
gapi.client
.load('https://www.googleapis.com/discovery/v1/apis/compute/v1/rest')
.then(() => {
mountGkeZoneDropdown();
mountGkeMachineTypeDropdown();
})
.catch(() => {
Flash(s__(GCP_API_ERROR));
});
}
document.addEventListener('DOMContentLoaded', () => {
if (typeof gapi === 'undefined') {
Flash(s__(GCP_API_ERROR));
return false;
}
gapi.load('client', initializeGapiClient);
});
<script>
import { __ } from '~/locale';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
export default {
components: {
LoadingIcon,
},
props: {
isDisabled: {
type: Boolean,
required: false,
default: false,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
toggleText: {
type: String,
required: true,
default: __('Select'),
},
},
computed: {},
};
</script>
<template>
<button
class="dropdown-menu-toggle dropdown-menu-full-width"
type="button"
data-toggle="dropdown"
aria-expanded="true"
:disabled="isDisabled || isLoading"
>
<loading-icon
v-show="isLoading"
:inline="true"
/>
<span class="dropdown-toggle-text">
{{ toggleText }}
</span>
<i
aria-hidden="true"
data-hidden="true"
class="fa fa-chevron-down"
></i>
</button>
</template>
<script>
import Flash from '~/flash';
import { s__ } from '~/locale';
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import eventHub from '../eventhub';
import store from '../stores';
import DropdownButton from './dropdown_button.vue';
// TODO: Fall back to default us-central1-a or first option
export default {
name: 'GkeMachineTypeDropdown',
store,
components: {
Icon,
LoadingIcon,
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
},
props: {
service: {
type: Object,
required: true,
},
fieldId: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
},
data() {
return {
isLoading: false,
hasErrors: false,
searchQuery: '',
selectedItem: '',
items: [],
};
},
computed: {
isDisabled() {
return (
this.$store.state.selectedProject.length === 0 ||
this.$store.state.selectedZone.length === 0
);
},
results() {
return this.items.filter(item => item.name.toLowerCase().indexOf(this.searchQuery) > -1);
},
toggleText() {
if (this.$store.state.selectedMachineType) {
return this.$store.state.selectedMachineType;
}
if (this.isLoading) {
return s__('ClusterIntegration|Fetching machine types');
}
if (!this.$store.state.selectedProject) {
return s__('ClusterIntegration|Select project and zone to choose machine type.');
}
return this.$store.state.selectedZone
? s__('ClusterIntegration|Select machine type')
: s__('ClusterIntegration|Select zone to choose machine type');
},
placeholderText() {
return s__('ClusterIntegration|Search machine types');
},
},
created() {
eventHub.$on('zoneSelected', this.fetchItems);
eventHub.$on('machineTypeSelected', this.enableSubmit);
},
methods: {
...mapActions(['setMachineType']),
fetchItems() {
this.isLoading = true;
const request = this.service.machineTypes.list({
project: this.$store.state.selectedProject,
zone: this.$store.state.selectedZone,
});
return request.then(
resp => {
this.items = resp.result.items;
// Cause error
// this.items = data;
// Single state
// this.items = [
// {
// create_time: '2018-01-16T15:55:02.992Z',
// lifecycle_state: 'ACTIVE',
// name: 'NaturalInterface',
// item_id: 'naturalinterface-192315',
// item_number: 840816084083,
// },
// ];
if (this.items.length === 1) {
this.isDisabled = true;
this.setMachineType(this.items[0].name);
}
this.isLoading = false;
},
() => {
this.isLoading = false;
this.hasErrors = true;
if (resp.result.error) {
Flash(
`${s__(
'ClusterIntegration|An error occured while trying to fetch zone machine types:',
)} ${resp.result.error.message}`,
);
}
},
this,
);
},
enableSubmit() {
document.querySelector('input[type=submit]').removeAttribute('disabled');
},
},
};
</script>
<template>
<div
class="dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
:value="$store.state.selectedProject"
/>
<dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }"
:is-disabled="isDisabled"
:is-loading="isLoading"
:toggle-text="toggleText"
/>
<div class="dropdown-menu dropdown-select">
<dropdown-search-input
v-model="searchQuery"
:placeholder-text="placeholderText"
/>
<div class="dropdown-content">
<ul>
<li
v-for="result in results"
:key="result.id"
>
<a
href="#"
@click.prevent="setMachineType(result.name)"
>{{ result.name }}</a>
</li>
</ul>
</div>
<div class="dropdown-loading">
<loading-icon />
</div>
</div>
</div>
</template>
<script>
import _ from 'underscore';
import Flash from '~/flash';
import { s__, sprintf } from '~/locale';
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import store from '../stores';
import DropdownButton from './dropdown_button.vue';
// TODO: Consolidate dropdown code
// TODO: Account for invalid project settings/errors (project returns error when retrieving zones)
export default {
name: 'GkeProjectIdDropdown',
store,
components: {
Icon,
LoadingIcon,
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
},
props: {
docsUrl: {
type: String,
required: true,
},
service: {
type: Object,
required: true,
},
fieldId: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
},
data() {
return {
isDisabled: false,
isLoading: true,
hasErrors: false,
searchQuery: '',
selectedItem: '',
items: [],
};
},
computed: {
results() {
return this.items.filter(item => item.name.toLowerCase().indexOf(this.searchQuery) > -1);
},
toggleText() {
if (this.$store.state.selectedProject) {
return this.$store.state.selectedProject;
}
return this.isLoading
? s__('ClusterIntegration|Fetching projects')
: s__('ClusterIntegration|Select project');
},
placeholderText() {
return s__('ClusterIntegration|Search projects');
},
helpText() {
let message;
if (this.hasErrors) {
message =
'ClusterIntegration|We were unable to fetch any projects. Ensure that you have a project on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.';
}
message = this.items.length
? 'ClusterIntegration|To use a new project, first create one on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.'
: 'ClusterIntegration|To create a cluster, first create a project on %{docsLinkStart}Google Cloud Platform%{docsLinkEnd}.';
return sprintf(
s__(message),
{
docsLinkEnd: '&nbsp;<i class="fa fa-external-link" aria-hidden="true"></i></a>',
docsLinkStart: `<a href="${_.escape(
this.docsUrl,
)}" target="_blank" rel="noopener noreferrer">`,
},
false,
);
},
},
created() {
this.fetchItems();
},
methods: {
...mapActions(['setProject']),
fetchItems() {
const request = this.service.projects.list();
return request.then(
resp => {
this.items = resp.result.projects;
// Cause error
// this.items = data;
// Single state
// this.items = [
// {
// create_time: '2018-01-16T15:55:02.992Z',
// lifecycle_state: 'ACTIVE',
// name: 'NaturalInterface',
// item_id: 'naturalinterface-192315',
// item_number: 840816084083,
// },
// ];
if (this.items.length === 1) {
this.isDisabled = true;
this.setProject(this.items[0].name);
}
this.isLoading = false;
},
resp => {
this.isLoading = false;
this.hasErrors = true;
if (resp.result.error) {
Flash(
`${s__('ClusterIntegration|An error occured while trying to fetch your projects:')} ${
resp.result.error.message
}`,
);
}
},
this,
);
},
},
};
</script>
<template>
<div>
<div
class="dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
:value="$store.state.selectedProject"
/>
<dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }"
:is-disabled="isDisabled"
:is-loading="isLoading"
:toggle-text="toggleText"
/>
<div class="dropdown-menu dropdown-select">
<dropdown-search-input
v-model="searchQuery"
:placeholder-text="placeholderText"
/>
<div class="dropdown-content">
<ul>
<li
v-for="result in results"
:key="result.project_number"
>
<a
href="#"
@click.prevent="setProject(result.name)"
>{{ result.name }}</a>
</li>
</ul>
</div>
<div class="dropdown-loading">
<loading-icon />
</div>
</div>
</div>
<span
class="help-block"
:class="{ 'gl-field-error-message': hasErrors }"
v-html="helpText"
></span>
</div>
</template>
<script>
import Flash from '~/flash';
import { s__ } from '~/locale';
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import eventHub from '../eventhub';
import store from '../stores';
import DropdownButton from './dropdown_button.vue';
// TODO: Fall back to default us-central1-a or first option
export default {
name: 'GkeZoneDropdown',
store,
components: {
Icon,
LoadingIcon,
DropdownButton,
DropdownSearchInput,
DropdownHiddenInput,
},
props: {
service: {
type: Object,
required: true,
},
fieldId: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
},
data() {
return {
isLoading: false,
hasErrors: false,
searchQuery: '',
selectedItem: '',
items: [],
};
},
computed: {
isDisabled() {
return this.$store.state.selectedProject.length === 0;
},
results() {
return this.items.filter(item => item.name.toLowerCase().indexOf(this.searchQuery) > -1);
},
toggleText() {
if (this.$store.state.selectedZone) {
return this.$store.state.selectedZone;
}
if (this.isLoading) {
return s__('ClusterIntegration|Fetching zones');
}
return this.$store.state.selectedProject
? s__('ClusterIntegration|Select zone')
: s__('ClusterIntegration|Select project to choose zone');
},
placeholderText() {
return s__('ClusterIntegration|Search zones');
},
},
created() {
eventHub.$on('projectSelected', this.fetchItems);
},
methods: {
...mapActions(['setZone']),
fetchItems() {
this.isLoading = true;
const request = this.service.zones.list({ project: this.$store.state.selectedProject });
return request.then(
resp => {
this.items = resp.result.items;
// Cause error
// this.items = data;
// Single state
// this.items = [
// {
// create_time: '2018-01-16T15:55:02.992Z',
// lifecycle_state: 'ACTIVE',
// name: 'NaturalInterface',
// item_id: 'naturalinterface-192315',
// item_number: 840816084083,
// },
// ];
if (this.items.length === 1) {
this.isDisabled = true;
this.setZone(this.items[0].name);
}
this.isLoading = false;
},
resp => {
this.isLoading = false;
this.hasErrors = true;
if (resp.result.error) {
Flash(
`${s__('ClusterIntegration|An error occured while trying to fetch project zones:')} ${
resp.result.error.message
}`,
);
}
},
this,
);
},
},
};
</script>
<template>
<div
class="dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
:value="$store.state.selectedProject"
/>
<dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }"
:is-disabled="isDisabled"
:is-loading="isLoading"
:toggle-text="toggleText"
/>
<div class="dropdown-menu dropdown-select">
<dropdown-search-input
v-model="searchQuery"
:placeholder-text="placeholderText"
/>
<div class="dropdown-content">
<ul>
<li
v-for="result in results"
:key="result.id"
>
<a
href="#"
@click.prevent="setZone(result.name)"
>{{ result.name }}</a>
</li>
</ul>
</div>
<div class="dropdown-loading">
<loading-icon />
</div>
</div>
</div>
</template>
import Vue from 'vue';
export default new Vue();
import * as types from './mutation_types';
import eventHub from '../eventhub';
export const setProject = ({ commit }, selectedProject) => {
commit(types.SET_PROJECT, selectedProject);
eventHub.$emit('projectSelected');
};
export const setZone = ({ commit }, selectedZone) => {
commit(types.SET_ZONE, selectedZone);
eventHub.$emit('zoneSelected');
};
export const setMachineType = ({ commit }, selectedMachineType) => {
commit(types.SET_MACHINE_TYPE, selectedMachineType);
eventHub.$emit('machineTypeSelected');
};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
actions,
getters,
mutations,
state: {
selectedProject: '',
selectedZone: '',
selectedMachineType: '',
},
});
export const SET_PROJECT = 'SET_PROJECT';
export const SET_ZONE = 'SET_ZONE';
export const SET_MACHINE_TYPE = 'SET_MACHINE_TYPE';
import * as types from './mutation_types';
export default {
[types.SET_PROJECT](state, selectedProject) {
Object.assign(state, { selectedProject });
},
[types.SET_ZONE](state, selectedZone) {
Object.assign(state, { selectedZone });
},
[types.SET_MACHINE_TYPE](state, selectedMachineType) {
Object.assign(state, { selectedMachineType });
},
};
<script>
export default {
props: {
name: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
},
};
</script>
<template>
<input
type="hidden"
:name="name"
:value="value"
/>
</template>
<script>
import { __ } from '~/locale';
export default {
props: {
searchQuery: {
type: String,
required: false,
default: '',
},
placeholderText: {
type: String,
required: true,
default: __('Search'),
},
},
};
</script>
<template>
<div class="dropdown-input">
<input
autocomplete="off"
class="dropdown-input-field"
type="search"
:placeholder="placeholderText"
:searchQuery="searchQuery"
@input="$emit('input', $event.target.value)"
/>
<i
aria-hidden="true"
class="fa fa-search dropdown-input-search"
data-hidden="true"
>
</i>
<i
aria-hidden="true"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear"
data-hidden="true"
role="button"
>
</i>
</div>
</template>
......@@ -26,3 +26,16 @@
margin-right: 0;
}
}
#new_cluster {
.dropdown-menu-toggle {
.loading-container {
.fa {
position: relative;
margin-top: 2px;
top: initial;
right: initial;
}
}
}
}
= javascript_include_tag 'https://apis.google.com/js/api.js'
%p
- link_to_help_page = link_to(s_('ClusterIntegration|help page'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
= s_('ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration.').html_safe % { link_to_help_page: link_to_help_page}
= form_for @cluster, html: { class: 'prepend-top-20' }, url: gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
= form_for @cluster, html: { class: 'prepend-top-20', data: { token: @token_in_session } }, url: gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
= form_errors(@cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name')
......@@ -14,13 +16,14 @@
= field.fields_for :provider_gcp, @cluster.provider_gcp do |provider_gcp_field|
.form-group
= provider_gcp_field.label :gcp_project_id, s_('ClusterIntegration|Google Cloud Platform project ID')
= link_to(s_('ClusterIntegration|See your projects'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer')
= provider_gcp_field.text_field :gcp_project_id, class: 'form-control', placeholder: s_('ClusterIntegration|Project ID')
#js-gcp-project-id-dropdown-entry-point{ data: { docsUrl: 'https://console.cloud.google.com/home/dashboard' } }
= provider_gcp_field.hidden_field :gcp_project_id, class: 'form-control', placeholder: 'Select project'
.form-group
= provider_gcp_field.label :zone, s_('ClusterIntegration|Zone')
= link_to(s_('ClusterIntegration|See zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
= provider_gcp_field.text_field :zone, class: 'form-control', placeholder: 'us-central1-a'
#js-gcp-zone-dropdown-entry-point
= provider_gcp_field.hidden_field :zone, class: 'form-control', placeholder: 'us-central1-a'
.form-group
= provider_gcp_field.label :num_nodes, s_('ClusterIntegration|Number of nodes')
......@@ -28,8 +31,8 @@
.form-group
= provider_gcp_field.label :machine_type, s_('ClusterIntegration|Machine type')
= link_to(s_('ClusterIntegration|See machine types'), 'https://cloud.google.com/compute/docs/machine-types', target: '_blank', rel: 'noopener noreferrer')
= provider_gcp_field.text_field :machine_type, class: 'form-control', placeholder: 'n1-standard-4'
#js-gcp-machine-type-dropdown-entry-point
= provider_gcp_field.hidden_field :machine_type, class: 'form-control', placeholder: 'us-central1-a'
.form-group
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'btn btn-success'
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'btn btn-success', disabled: true
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment