From 566cc51f58d1b012ad133f57e42f8da671dc0de8 Mon Sep 17 00:00:00 2001 From: Alexandr Promakh Date: Sat, 6 Jul 2019 14:10:18 +0500 Subject: [PATCH] ServiceForm autocompletes --- src/renderer/components/shared/Loader.vue | 35 ++++++- .../shared/form/AutocompleteInput.vue | 84 ++++++++++++++++ .../components/shared/service/ServiceForm.vue | 98 +++++++++++++++++-- src/renderer/lib/helpers/cluster.js | 9 +- 4 files changed, 214 insertions(+), 12 deletions(-) create mode 100644 src/renderer/components/shared/form/AutocompleteInput.vue diff --git a/src/renderer/components/shared/Loader.vue b/src/renderer/components/shared/Loader.vue index 424696d..27f7403 100644 --- a/src/renderer/components/shared/Loader.vue +++ b/src/renderer/components/shared/Loader.vue @@ -1,7 +1,24 @@ + + diff --git a/src/renderer/components/shared/service/ServiceForm.vue b/src/renderer/components/shared/service/ServiceForm.vue index d95ec1d..b3c8d54 100644 --- a/src/renderer/components/shared/service/ServiceForm.vue +++ b/src/renderer/components/shared/service/ServiceForm.vue @@ -6,7 +6,10 @@ - + @@ -24,7 +27,11 @@ :disabled="!attributes.workloadType" > @@ -51,8 +58,10 @@ import cloneDeep from 'clone-deep' import { mapActions } from 'vuex' import { required, minLength, integer, between } from 'vuelidate/lib/validators' import { validationMixin } from 'vuelidate' +import { Core_v1Api, Extensions_v1beta1Api } from '@kubernetes/client-node' // eslint-disable-line camelcase -import * as workloadTypes from '../../../lib/constants/workload-types' +import * as resourceKinds from '../../../lib/constants/workload-types' +import * as clusterHelper from '../../../lib/helpers/cluster' import BaseForm from '../form/BaseForm' import BaseInput from '../form/BaseInput' @@ -60,9 +69,11 @@ import BaseSelect from '../form/BaseSelect' import Button from '../Button' import ForwardsTable from './ForwardsTable' import ControlGroup from '../form/ControlGroup' +import AutocompleteInput from '../form/AutocompleteInput' export default { components: { + AutocompleteInput, ControlGroup, BaseInput, BaseForm, @@ -82,7 +93,7 @@ export default { namespace: { required }, workloadType: { required, - oneOf: (value) => Object.values(workloadTypes).includes(value) + oneOf: (value) => Object.values(resourceKinds).includes(value) }, workloadName: { required }, forwards: { @@ -102,6 +113,16 @@ export default { ...this.getEmptyAttributes(), clusterId: this.$route.params.clusterId, ...cloneDeep(this.initialAttributes) + }, + namespaces: { + data: [], + loading: false, + clusterId: null + }, + resources: { + data: [], + loading: false, + cacheKey: null } } }, @@ -111,9 +132,9 @@ export default { }, workloadTypeOptions() { return [ - [workloadTypes.POD, 'Pod'], - [workloadTypes.DEPLOYMENT, 'Deployment'], - [workloadTypes.SERVICE, 'Service'] + [resourceKinds.POD, 'Pod'], + [resourceKinds.DEPLOYMENT, 'Deployment'], + [resourceKinds.SERVICE, 'Service'] ] }, submitButtonTitle() { @@ -121,6 +142,21 @@ export default { }, backPath() { return '/' + }, + cluster() { + return this.$store.state.Clusters.items[this.attributes.clusterId] + }, + coreApi() { + try { + return clusterHelper.buildApiClient(this.cluster, Core_v1Api) + } catch (e) { + console.error(e) + return null + } + }, + resourcesCacheKey() { + const { clusterId, namespace, workloadType } = this.attributes + return `${clusterId}:${namespace}:${workloadType}` } }, methods: { @@ -135,6 +171,54 @@ export default { forwards: [] } }, + async handleNamespaceFocus() { + if (this.coreApi && this.namespaces.clusterId !== this.attributes.clusterId) { + this.namespaces.loading = true + + try { + const namespaces = (await this.coreApi.listNamespace()).body.items + this.namespaces.data = namespaces.map(x => x.metadata.name) + this.namespaces.clusterId = this.cluster.id + } catch (e) { + console.error(e) + } + + this.namespaces.loading = false + } + }, + async handleResourceNameFocus() { + if (this.coreApi && this.resources.cacheKey !== this.resourcesCacheKey) { + this.resources.loading = true + + try { + this.resources.data = await this.getResources( + this.coreApi, + this.attributes.workloadType, + this.attributes.namespace + ) + this.resources.cacheKey = this.resourcesCacheKey + } catch (e) { + console.error(e) + } + + this.resources.loading = false + } + }, + async getResources(coreApi, kind, namespace) { + if (kind === resourceKinds.POD) { + const response = await coreApi.listNamespacedPod(namespace) + return response.body.items.map(x => x.metadata.name) + } else if (kind === resourceKinds.DEPLOYMENT) { + const extensionsApi = clusterHelper.buildApiClient(this.cluster, Extensions_v1beta1Api) + const response = await extensionsApi.listNamespacedDeployment(namespace) + return response.body.items.map(x => x.metadata.name) + } else if (kind === resourceKinds.SERVICE) { + const response = await coreApi.listNamespacedService(namespace) + return response.body.items.map(x => x.metadata.name) + } + + return [] + }, handleSubmit() { const action = this.serviceId ? 'Services/updateService' : 'Services/createService' diff --git a/src/renderer/lib/helpers/cluster.js b/src/renderer/lib/helpers/cluster.js index f6d7c31..b405d8d 100644 --- a/src/renderer/lib/helpers/cluster.js +++ b/src/renderer/lib/helpers/cluster.js @@ -1,4 +1,4 @@ -import { Core_v1Api } from '@kubernetes/client-node' // eslint-disable-line camelcase +import { Core_v1Api, KubeConfig } from '@kubernetes/client-node' // eslint-disable-line camelcase import { k8nApiPrettyError } from './k8n-api-error' @@ -23,3 +23,10 @@ export async function checkConnection(kubeConfig, context = null) { return error } + +// You must catch errors manually +export function buildApiClient(cluster, api) { + const kubeConfig = new KubeConfig() + kubeConfig.loadFromString(cluster.config) + return kubeConfig.makeApiClient(api) +}