compute: add CKS support (#247)

Adds CKS UI support in Primate.

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Abhishek Kumar 2020-03-25 18:22:08 +05:30 committed by Rohit Yadav
parent 0aeda824ee
commit 0b8867e6d5
11 changed files with 1867 additions and 11 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -24,7 +24,8 @@
<div class="avatar">
<slot name="avatar">
<os-logo v-if="resource.ostypeid || resource.ostypename" :osId="resource.ostypeid" :osName="resource.ostypename" size="4x" />
<a-icon v-else style="font-size: 36px" :type="$route.meta.icon" />
<a-icon v-else-if="typeof $route.meta.icon ==='string'" style="font-size: 36px" :type="$route.meta.icon" />
<a-icon v-else style="font-size: 36px" :component="$route.meta.icon" />
</slot>
</div>
<slot name="name">

View File

@ -15,6 +15,8 @@
// specific language governing permissions and limitations
// under the License.
import kubernetes from '@/assets/icons/kubernetes.svg?inline'
export default {
name: 'compute',
title: 'Compute',
@ -328,15 +330,74 @@ export default {
}
]
},
/*
{
name: 'demo',
title: 'Demo',
icon: 'radar-chart',
permission: [ 'listVirtualMachines' ],
component: () => import('@/components/Test.vue')
name: 'kubernetes',
title: 'Kubernetes',
icon: kubernetes,
permission: ['listKubernetesClusters'],
columns: ['name', 'state', 'size', 'cpunumber', 'memory', 'account', 'zonename'],
details: ['name', 'description', 'zonename', 'kubernetesversionname', 'size', 'masternodes', 'cpunumber', 'memory', 'keypair', 'associatednetworkname', 'account', 'domain', 'zonename'],
tabs: [{
name: 'k8s',
component: () => import('@/views/compute/KubernetesServiceTab.vue')
}],
actions: [
{
api: 'createKubernetesCluster',
icon: 'plus',
label: 'Create Kubernetes Cluster',
listView: true,
popup: true,
component: () => import('@/views/compute/CreateKubernetesCluster.vue')
},
{
api: 'startKubernetesCluster',
icon: 'caret-right',
label: 'Start Kubernetes Cluster',
dataView: true,
show: (record) => { return ['Stopped'].includes(record.state) }
},
{
api: 'stopKubernetesCluster',
icon: 'stop',
label: 'Stop Kubernetes Cluster',
dataView: true,
show: (record) => { return !['Stopped'].includes(record.state) }
},
// {
// api: 'getKubernetesClusterConfig',
// icon: 'cloud-download',
// label: 'Download Cluster Config',
// dataView: true,
// show: (record) => { return !['Stopped'].includes(record.state) }
// },
{
api: 'scaleKubernetesCluster',
icon: 'swap',
label: 'Scale Kubernetes Cluster',
dataView: true,
show: (record) => { return ['Created', 'Running'].includes(record.state) },
popup: true,
component: () => import('@/views/compute/ScaleKubernetesCluster.vue')
},
{
api: 'upgradeKubernetesCluster',
icon: 'plus-circle',
label: 'Upgrade Kubernetes Cluster',
dataView: true,
show: (record) => { return ['Created', 'Running'].includes(record.state) },
popup: true,
component: () => import('@/views/compute/UpgradeKubernetesCluster.vue')
},
{
api: 'deleteKubernetesCluster',
icon: 'delete',
label: 'Delete Kubernetes Cluster',
dataView: true,
show: (record) => { return !['Destroyed', 'Destroying'].includes(record.state) }
}
]
},
*/
{
name: 'vmgroup',
title: 'Instance Groups',

View File

@ -15,6 +15,8 @@
// specific language governing permissions and limitations
// under the License.
import kubernetes from '@/assets/icons/kubernetes.svg?inline'
export default {
name: 'image',
title: 'Images',
@ -196,6 +198,38 @@ export default {
groupAction: true
}
]
},
{
name: 'kubernetesiso',
title: 'Kubernetes ISOs',
icon: kubernetes,
permission: ['listKubernetesSupportedVersions'],
columns: ['name', 'state', 'semanticversion', 'isostate', 'mincpunumber', 'minmemory', 'zonename'],
details: ['name', 'semanticversion', 'zoneid', 'zonename', 'isoid', 'isoname', 'isostate', 'mincpunumber', 'minmemory', 'supportsha', 'state'],
actions: [
{
api: 'addKubernetesSupportedVersion',
icon: 'plus',
label: 'Add Kubernetes Version',
listView: true,
popup: true,
component: () => import('@/views/image/AddKubernetesSupportedVersion.vue')
},
{
api: 'updateKubernetesSupportedVersion',
icon: 'edit',
label: 'Update Kuberntes Version',
dataView: true,
popup: true,
component: () => import('@/views/image/UpdateKubernetesSupportedVersion.vue')
},
{
api: 'deleteKubernetesSupportedVersion',
icon: 'delete',
label: 'Delete Kubernetes Version',
dataView: true
}
]
}
]
}

View File

@ -57,6 +57,7 @@
"Virtual Routers": "Virtual Routers",
"Volumes": "Volumes",
"Zones": "Zones",
"access": "Access",
"accesskey": "Access Key",
"account": "Account",
"accountId": "Account",
@ -114,9 +115,10 @@
"certificate": "Certificate",
"certificateid": "Certificate ID",
"chassis": "Chassis",
"checksum": "checksum",
"checksum": "Checksum",
"cidr": "Super CIDR for Guest Networks",
"cidrlist": "CIDR list",
"cks.cluster.size": "Cluster size (Worker nodes)",
"cleanup": "Clean up",
"clusterId": "Cluster",
"clusterid": "Cluster",
@ -209,9 +211,11 @@
"esplifetime": "ESP Lifetime (second)",
"esppolicy": "ESP policy",
"expunge": "Expunge",
"externalloadbalanceripaddress": "External load balancer IP address",
"externalid": "External Id",
"extra": "Extra Arguments",
"fingerprint": "FingerPrint",
"firewall": "Firewall",
"firstname": "First Name",
"forced": "Force Stop",
"forceencap": "Force UDP Encapsulation of ESP Packets",
@ -321,11 +325,13 @@
"isextractable": "Extractable",
"isfeatured": "Featured",
"iso": "ISO",
"isoid": "ISO",
"isolatedpvlantype": "Secondary Isolated VLAN Type",
"isolatedpvlanid": "Secondary Isolated VLAN ID",
"isolationmethods": "Isolation method",
"isolationuri": "Isolation URI",
"isoname": "Attached ISO",
"isostate": "ISO State",
"ispersistent": "Persistent ",
"isportable": "Cross Zones",
"ispublic": "Public",
@ -340,6 +346,8 @@
"key": "Key",
"keyboardType": "Keyboard type",
"keypair": "SSH Key Pair",
"kubernetesversionid": "Kubernetes version",
"kubernetesversionname": "Kubernetes version",
"kvmnetworklabel": "KVM Traffic Label",
"l2gatewayserviceuuid": "L2 Gateway Service Uuid",
"l3gatewayserviceuuid": "L3 Gateway Service Uuid",
@ -681,6 +689,7 @@
"limitcpuuse": "CPU Cap",
"linklocalip": "Link Local IP Address",
"loadbalancerinstance": "Assigned VMs",
"loadbalancing": "Load Balancing",
"loadbalancerrule": "Load balancing rule",
"localstorageenabled": "Enable local storage for User VMs",
"localstorageenabledforsystemvm": "Enable local storage for System VMs",
@ -691,6 +700,7 @@
"makeredundant": "Make redundant",
"managedstate": "Managed State",
"managementServers": "Number of Management Servers",
"masternodes": "Master nodes",
"maxuser_vm": "Max. user VMs",
"maxpublic_ip": "Max. public IPs",
"maxvolume": "Max. volumes",
@ -728,10 +738,10 @@
"message.network.removeNIC": "Please confirm that want to remove this NIC, which will also remove the associated network from the VM.",
"message.network.secondaryIP" : "Please confirm that you would like to acquire a new secondary IP for this NIC. \n NOTE: You need to manually configure the newly-acquired secondary IP inside the virtual machine.",
"message.network.updateIp": "Please confirm that you would like to change the IP address for this NIC on VM.",
"minCPUNumber": "Min CPU Cores",
"mincpunumber": "Min CPU Cores",
"minInstance": "Min Instances",
"minIops": "Min IOPS",
"minMemory": "Min Memory (in MB)",
"minmemory": "Min Memory (in MB)",
"min_balance": "Min Balance",
"miniops": "Min IOPS",
"name": "Name",
@ -766,6 +776,7 @@
"nfsCacheZoneid": "Zone",
"nfsServer": "NFS Server",
"nicAdapterType": "NIC adapter type",
"noderootdisksize": "Node root disk size (in GB)",
"number": "#Rule",
"numberOfRouterRequiresUpgrade": "Total of Virtual Routers that require upgrade",
"numretries": "Number of Retries",
@ -806,6 +817,7 @@
"podname": "Pod name",
"port": "Port",
"portableipaddress": "Portable IPs",
"portforwarding": "Port Forwarding",
"powerstate": "Power State",
"primaryStorageLimit": "Primary Storage limits (GiB)",
"primarystoragetotal": "Primary Storage",
@ -889,6 +901,7 @@
"securityGroups": "Security Groups",
"securitygroup": "Security Group",
"select": "Select",
"semanticversion": "Semantic version",
"sent": "Date",
"sentbytes": "Bytes Sent",
"server": "Server",

View File

@ -0,0 +1,442 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout">
<a-spin :spinning="loading">
<a-form
:form="form"
@submit="handleSubmit"
layout="vertical">
<a-form-item :label="$t('name')">
<a-input
v-decorator="['name', {
rules: [{ required: true, message: 'Please enter Kubernetes cluster name' }]
}]"
:placeholder="apiParams.name.description"/>
</a-form-item>
<a-form-item :label="$t('description')">
<a-input
v-decorator="['description', {
rules: [{ message: 'Please enter name' }]
}]"
:placeholder="apiParams.description.description"/>
</a-form-item>
<a-form-item :label="$t('zoneid')">
<a-select
id="zone-selection"
v-decorator="['zoneid', {
rules: [{ required: true }]
}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="zoneLoading"
:placeholder="apiParams.zoneid.description"
@change="val => { this.handleZoneChanged(this.zones[val]) }">
<a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('kubernetesversionid')">
<a-select
id="version-selection"
v-decorator="['kubernetesversionid', {
rules: [{ required: true }]
}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="kubernetesVersionLoading"
:placeholder="apiParams.kubernetesversionid.description"
@change="val => { this.handleKubernetesVersionChange(this.kubernetesVersions[val]) }">
<a-select-option v-for="(opt, optIndex) in this.kubernetesVersions" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('serviceofferingid')">
<a-select
id="offering-selection"
v-decorator="['serviceofferingid', {
rules: [{ required: true }]
}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="serviceOfferingLoading"
:placeholder="apiParams.serviceofferingid.description">
<a-select-option v-for="(opt, optIndex) in this.serviceOfferings" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('noderootdisksize')">
<a-input
v-decorator="['noderootdisksize', {
rules: [{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback('Please enter a valid number')
}
callback()
}
}]
}]"
:placeholder="apiParams.noderootdisksize.description"/>
</a-form-item>
<a-form-item :label="$t('networkid')">
<a-select
id="network-selection"
v-decorator="['networkid', {}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="networkLoading"
:placeholder="apiParams.networkid.description">
<a-select-option v-for="(opt, optIndex) in this.networks" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('haenable')" v-if="this.selectedKubernetesVersion != null && this.selectedKubernetesVersion != undefined && this.selectedKubernetesVersion.supportsha === true">
<a-switch v-decorator="['haenable', {initialValue: this.haEnabled}]" :checked="this.haEnabled" @change="val => { this.haEnabled = val }" />
</a-form-item>
<a-form-item :label="$t('masternodes')" v-if="this.haEnabled">
<a-input
v-decorator="['masternodes', {
initialValue: '1',
rules: [{ required: true, message: 'Please enter value' },
{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback('Please enter a valid number')
}
callback()
}
}
]
}]"
:placeholder="apiParams.masternodes.description"/>
</a-form-item>
<a-form-item :label="$t('externalloadbalanceripaddress')" v-if="this.haEnabled">
<a-input
v-decorator="['externalloadbalanceripaddress', {}]"
:placeholder="apiParams.externalloadbalanceripaddress.description"/>
</a-form-item>
<a-form-item :label="$t('cks.cluster.size')">
<a-input
v-decorator="['size', {
initialValue: '1',
rules: [{ required: true, message: 'Please enter value' },
{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback('Please enter a valid number')
}
callback()
}
}
]
}]"
:placeholder="apiParams.size.description"/>
</a-form-item>
<a-form-item :label="$t('keypair')">
<a-select
id="keypair-selection"
v-decorator="['keypair', {}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="keyPairLoading"
:placeholder="apiParams.keypair.description">
<a-select-option v-for="(opt, optIndex) in this.keyPairs" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
<a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { api } from '@/api'
export default {
name: 'CreateKubernetesCluster',
props: {},
data () {
return {
zones: [],
zoneLoading: false,
selectedZone: {},
kubernetesVersions: [],
kubernetesVersionLoading: false,
selectedKubernetesVersion: {},
serviceOfferings: [],
serviceOfferingLoading: false,
networks: [],
networkLoading: false,
keyPairs: [],
keyPairLoading: false,
haEnabled: false,
loading: false
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
this.apiConfig = this.$store.getters.apis.createKubernetesCluster || {}
this.apiParams = {}
this.apiConfig.params.forEach(param => {
this.apiParams[param.name] = param
})
},
created () {
this.networks = [
{
id: null,
name: ''
}
]
this.keyPairs = [
{
id: null,
name: ''
}
]
},
mounted () {
this.fetchData()
},
methods: {
fetchData () {
this.fetchZoneData()
this.fetchNetworkData()
this.fetchKeyPairData()
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
fetchZoneData () {
const params = {}
this.zoneLoading = true
api('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
this.zones = this.zones.concat(listZones)
}).finally(() => {
this.zoneLoading = false
if (this.arrayHasItems(this.zones)) {
this.form.setFieldsValue({
zoneid: 0
})
this.handleZoneChange(this.zones[0])
}
})
},
handleZoneChange (zone) {
this.selectedZone = zone
this.fetchKubernetesVersionData()
},
fetchKubernetesVersionData () {
this.kubernetesVersions = []
const params = {}
if (!this.isObjectEmpty(this.selectedZone)) {
params.zoneid = this.selectedZone.id
}
this.kubernetesVersionLoading = true
api('listKubernetesSupportedVersions', params).then(json => {
const versionObjs = json.listkubernetessupportedversionsresponse.kubernetessupportedversion
if (this.arrayHasItems(versionObjs)) {
for (var i = 0; i < versionObjs.length; i++) {
if (versionObjs[i].state === 'Enabled' && versionObjs[i].isostate === 'Ready') {
this.kubernetesVersions.push(versionObjs[i])
}
}
}
}).finally(() => {
this.kubernetesVersionLoading = false
if (this.arrayHasItems(this.kubernetesVersions)) {
this.form.setFieldsValue({
kubernetesversionid: 0
})
this.handleKubernetesVersionChange(this.kubernetesVersions[0])
}
})
},
handleKubernetesVersionChange (version) {
this.selectedKubernetesVersion = version
this.fetchServiceOfferingData()
},
fetchServiceOfferingData () {
this.serviceOfferings = []
const params = {}
this.serviceOfferingLoading = true
api('listServiceOfferings', params).then(json => {
var items = json.listserviceofferingsresponse.serviceoffering
var minCpu = 2
var minMemory = 2048
if (!this.isObjectEmpty(this.selectedKubernetesVersion)) {
minCpu = this.selectedKubernetesVersion.mincpunumber
minMemory = this.selectedKubernetesVersion.minmemory
}
if (items != null) {
for (var i = 0; i < items.length; i++) {
if (items[i].iscustomized === false &&
items[i].cpunumber >= minCpu && items[i].memory >= minMemory) {
this.serviceOfferings.push(items[i])
}
}
}
}).finally(() => {
this.serviceOfferingLoading = false
if (this.arrayHasItems(this.serviceOfferings)) {
this.form.setFieldsValue({
serviceofferingid: 0
})
}
})
},
fetchNetworkData () {
const params = {}
this.networkLoading = true
api('listNetworks', params).then(json => {
const listNetworks = json.listnetworksresponse.network
if (this.arrayHasItems(listNetworks)) {
this.networks = this.networks.concat(listNetworks)
}
}).finally(() => {
this.networkLoading = false
if (this.arrayHasItems(this.networks)) {
this.form.setFieldsValue({
networkid: 0
})
}
})
},
fetchKeyPairData () {
const params = {}
this.keyPairLoading = true
api('listSSHKeyPairs', params).then(json => {
const listKeyPairs = json.listsshkeypairsresponse.sshkeypair
if (this.arrayHasItems(listKeyPairs)) {
for (var i = 0; i < listKeyPairs.length; i++) {
this.keyPairs.push({
id: listKeyPairs[i].name,
description: listKeyPairs[i].name
})
}
}
}).finally(() => {
this.keyPairLoading = false
if (this.arrayHasItems(this.keyPairs)) {
this.form.setFieldsValue({
keypair: 0
})
}
})
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
this.loading = true
const params = {
name: values.name,
description: values.description,
zoneid: this.zones[values.zoneid].id,
kubernetesversionid: this.kubernetesVersions[values.kubernetesversionid].id,
serviceofferingid: this.serviceOfferings[values.serviceofferingid].id,
size: values.size
}
if (this.isValidValueForKey(values, 'noderootdisksize') && values.noderootdisksize > 0) {
params.noderootdisksize = values.noderootdisksize
}
if (this.isValidValueForKey(values, 'masternodes') && values.masternodes > 0) {
params.masternodes = values.masternodes
}
if (this.isValidValueForKey(values, 'externalloadbalanceripaddress') && values.externalloadbalanceripaddress !== '') {
params.externalloadbalanceripaddress = values.externalloadbalanceripaddress
}
if (this.isValidValueForKey(values, 'networkid') && this.arrayHasItems(this.networks) && this.networks[values.networkid].id != null) {
params.networkid = this.networks[values.networkid].id
}
if (this.isValidValueForKey(values, 'keypair') && this.arrayHasItems(this.keyPairs) && this.keyPairs[values.keypair].id != null) {
params.keypair = this.keyPairs[values.keypair].id
}
api('createKubernetesCluster', params).then(json => {
this.$message.success('Successfully created Kubernetes cluster: ' + values.name)
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})
}).finally(() => {
this.$emit('refresh-data')
this.loading = false
this.closeAction()
})
})
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 80vw;
@media (min-width: 700px) {
width: 550px;
}
}
.action-button {
text-align: right;
button {
margin-right: 5px;
}
}
</style>

View File

@ -0,0 +1,432 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<a-spin :spinning="networkLoading">
<a-tabs
:activeKey="currentTab"
:tabPosition="device === 'tablet' || device === 'mobile' ? 'top' : 'left'"
:animated="false"
@change="handleChangeTab">
<a-tab-pane :tab="$t('details')" key="details">
<DetailsTab :resource="resource" :loading="loading" />
</a-tab-pane>
<a-tab-pane :tab="$t('access')" key="access">
<a-card title="Kubernetes Cluster Config" :loading="this.versionLoading">
<div v-if="this.clusterConfig !== ''">
<a-textarea :value="this.clusterConfig" :rows="5" readonly />
<div :span="24" class="action-button">
<a-button @click="downloadKubernetesClusterConfig" type="primary">{{ this.$t('Download') }}</a-button>
</div>
</div>
<div v-else>
<p>Kubernetes cluster kubeconfig not available currently</p>
</div>
</a-card>
<a-card title="Using CLI" :loading="this.versionLoading">
<a-timeline>
<a-timeline-item>
<p>
Download kubeconfig for the cluster<br><br>
The <code>kubectl</code> command-line tool uses kubeconfig files to find the information it needs to choose a cluster and communicate with the API server of a cluster.
</p>
</a-timeline-item>
<a-timeline-item>
<p>
Download <code>kubectl</code> tool for cluster's Kubernetes version<br><br>
Linux: <a :href="this.kubectlLinuxLink">{{ this.kubectlLinuxLink }}</a><br>
MacOS: <a :href="this.kubectlMacLink">{{ this.kubectlMacLink }}</a><br>
Windows: <a :href="this.kubectlWindowsLink">{{ this.kubectlWindowsLink }}</a>
</p>
</a-timeline-item>
<a-timeline-item>
<p>
Use <code>kubectl</code> and <code>kubeconfig</code> file to access cluster<br><br>
<code><b>kubectl --kubeconfig /custom/path/kube.conf {COMMAND}</b></code><br><br>
<em>List pods</em><br>
<code>kubectl --kubeconfig /custom/path/kube.conf get pods --all-namespaces</code><br>
<em>List nodes</em><br>
<code>kubectl --kubeconfig /custom/path/kube.conf get nodes --all-namespaces</code><br>
<em>List services</em><br>
<code>kubectl --kubeconfig /custom/path/kube.conf get services --all-namespaces</code>
</p>
</a-timeline-item>
</a-timeline>
</a-card>
<a-card title="Kubernetes Dashboard UI">
<a-timeline>
<a-timeline-item>
<p>
Run proxy locally<br><br>
<code><b>kubectl --kubeconfig /custom/path/kube.conf proxy</b></code>
</p>
</a-timeline-item>
<a-timeline-item>
<p>
Open URL in browser<br><br>
<a href="http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/"><code>http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/</code></a>
</p>
</a-timeline-item>
<a-timeline-item>
<p>
Token for dashboard login can be retrieved using following command<br><br>
<code><b>kubectl --kubeconfig /custom/path/kube.conf describe secret $(kubectl --kubeconfig /custom/path/kube.conf get secrets -n kubernetes-dashboard | grep kubernetes-dashboard-token | awk '{print $1}') -n kubernetes-dashboard</b></code>
</p>
</a-timeline-item>
</a-timeline>
<p>More about accessing dashboard UI, <a href="https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#accessing-the-dashboard-ui">https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#accessing-the-dashboard-ui</a></p>
</a-card>
</a-tab-pane>
<a-tab-pane :tab="$t('instances')" key="instances">
<a-table
class="table"
size="small"
:columns="this.vmColumns"
:dataSource="this.virtualmachines"
:rowKey="item => item.id"
:pagination="false"
>
<template slot="name" slot-scope="text, record">
<router-link :to="{ path: '/vm/' + record.id }">{{ record.name }}</router-link>
</template>
<template slot="state" slot-scope="text">
<status :text="text ? text : ''" displayText />
</template>
</a-table>
</a-tab-pane>
<a-tab-pane :tab="$t('firewall')" key="firewall">
<FirewallRules :resource="this.publicIpAddress" :loading="this.networkLoading" />
</a-tab-pane>
<a-tab-pane :tab="$t('portforwarding')" key="portforwarding">
<PortForwarding :resource="this.publicIpAddress" :loading="this.networkLoading" />
</a-tab-pane>
<a-tab-pane :tab="$t('loadbalancing')" key="loadbalancing">
<LoadBalancing :resource="this.publicIpAddress" :loading="this.networkLoading" />
</a-tab-pane>
</a-tabs>
</a-spin>
</template>
<script>
import { api } from '@/api'
import { mixinDevice } from '@/utils/mixin.js'
import DetailsTab from '@/components/view/DetailsTab'
import FirewallRules from '@/views/network/FirewallRules'
import PortForwarding from '@/views/network/PortForwarding'
import LoadBalancing from '@/views/network/LoadBalancing'
import Status from '@/components/widgets/Status'
export default {
name: 'KubernetesServiceTab',
components: {
DetailsTab,
FirewallRules,
PortForwarding,
LoadBalancing,
Status
},
mixins: [mixinDevice],
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
clusterConfigLoading: false,
clusterConfig: '',
versionLoading: false,
kubernetesVersion: {},
kubectlLinuxLink: 'https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/linux/amd64/kubectl',
kubectlMacLink: 'https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/darwin/amd64/kubectl',
kubectlWindowsLink: 'https://storage.googleapis.com/kubernetes-release/release/v1.16.0/bin/windows/amd64/kubectl.exe',
instanceLoading: false,
virtualmachines: [],
vmColumns: [],
networkLoading: false,
network: {},
publicIpAddress: {},
currentTab: 'details'
}
},
created () {
if (this.isAdminOrDomainAdmin()) {
this.vmColumns = [
{
title: this.$t('name'),
dataIndex: 'name',
scopedSlots: { customRender: 'name' }
},
{
title: this.$t('state'),
dataIndex: 'state',
scopedSlots: { customRender: 'state' }
},
{
title: this.$t('instancename'),
dataIndex: 'instancename'
},
{
title: this.$t('ipaddress'),
dataIndex: 'ipaddress'
},
{
title: this.$t('zonename'),
dataIndex: 'zonename'
}
]
} else {
this.vmColumns = [
{
title: this.$t('name'),
dataIndex: 'name'
},
{
title: this.$t('displayname'),
dataIndex: 'displayname'
},
{
title: this.$t('ipaddress'),
dataIndex: 'ipaddress'
},
{
title: this.$t('zonename'),
dataIndex: 'zonename'
},
{
title: this.$t('state'),
dataIndex: 'state'
}
]
}
},
mounted () {
this.handleFetchData()
},
watch: {
loading (newData, oldData) {
if (!newData && this.resource.id) {
this.handleFetchData()
}
}
},
methods: {
isAdminOrDomainAdmin () {
return ['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
handleChangeTab (e) {
this.currentTab = e
},
handleFetchData () {
this.fetchKubernetesClusterConfig()
this.fetchKubernetesVersion()
this.fetchInstances()
this.fetchPublicIpAddress()
},
fetchKubernetesClusterConfig () {
this.clusterConfigLoading = true
this.clusterConfig = ''
if (!this.isObjectEmpty(this.resource)) {
var params = {}
params.id = this.resource.id
api('getKubernetesClusterConfig', params).then(json => {
const config = json.getkubernetesclusterconfigresponse.clusterconfig
if (!this.isObjectEmpty(config) &&
this.isValidValueForKey(config, 'configdata') &&
config.configdata !== '') {
this.clusterConfig = config.configdata
} else {
this.$notification.error({
message: 'Request Failed',
description: 'Unable to retrieve Kubernetes cluster config'
})
}
}).finally(() => {
this.clusterConfigLoading = false
if (!this.isObjectEmpty(this.kubernetesVersion) && this.isValidValueForKey(this.kubernetesVersion, 'semanticversion')) {
this.kubectlLinuxLink = 'https://storage.googleapis.com/kubernetes-release/release/v' + this.kubernetesVersion.semanticversion + '/bin/linux/amd64/kubectl'
this.kubectlMacLink = 'https://storage.googleapis.com/kubernetes-release/release/v' + this.kubernetesVersion.semanticversion + '/bin/darwin/amd64/kubectl'
this.kubectlWindowsLink = 'https://storage.googleapis.com/kubernetes-release/release/v' + this.kubernetesVersion.semanticversion + '/bin/windows/amd64/kubectl.exe'
}
})
}
},
fetchKubernetesVersion () {
this.versionLoading = true
this.virtualmachines = []
if (!this.isObjectEmpty(this.resource) && this.isValidValueForKey(this.resource, 'kubernetesversionid') &&
this.resource.kubernetesversionid !== '') {
var params = {}
params.id = this.resource.kubernetesversionid
api('listKubernetesSupportedVersions', params).then(json => {
const versionObjs = json.listkubernetessupportedversionsresponse.kubernetessupportedversion
if (this.arrayHasItems(versionObjs)) {
this.kubernetesVersion = versionObjs[0]
}
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: error.response.headers['x-description']
})
}).finally(() => {
this.versionLoading = false
if (!this.isObjectEmpty(this.kubernetesVersion) && this.isValidValueForKey(this.kubernetesVersion, 'semanticversion')) {
this.kubectlLinuxLink = 'https://storage.googleapis.com/kubernetes-release/release/v' + this.kubernetesVersion.semanticversion + '/bin/linux/amd64/kubectl'
this.kubectlMacLink = 'https://storage.googleapis.com/kubernetes-release/release/v' + this.kubernetesVersion.semanticversion + '/bin/darwin/amd64/kubectl'
this.kubectlWindowsLink = 'https://storage.googleapis.com/kubernetes-release/release/v' + this.kubernetesVersion.semanticversion + '/bin/windows/amd64/kubectl.exe'
}
})
}
},
fetchInstances () {
this.instanceLoading = true
this.virtualmachines = []
if (!this.isObjectEmpty(this.resource) && this.arrayHasItems(this.resource.virtualmachineids)) {
var params = {}
if (this.isValidValueForKey(this.resource, 'projectid') &&
this.resource.projectid !== '') {
params.projectid = this.resource.projectid
}
params.ids = this.resource.virtualmachineids.join()
api('listVirtualMachines', params).then(json => {
const listVms = json.listvirtualmachinesresponse.virtualmachine
if (this.arrayHasItems(listVms)) {
for (var i = 0; i < listVms.length; ++i) {
var vm = listVms[i]
if (vm.nic && vm.nic.length > 0 && vm.nic[0].ipaddress) {
vm.ipaddress = vm.nic[0].ipaddress
listVms[i] = vm
}
}
this.virtualmachines = this.virtualmachines.concat(listVms)
}
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: error.response.headers['x-description']
})
}).finally(() => {
this.instanceLoading = false
})
}
},
fetchPublicIpAddress () {
this.networkLoading = true
var params = {
listAll: true,
forvirtualnetwork: true
}
if (!this.isObjectEmpty(this.resource)) {
if (this.isValidValueForKey(this.resource, 'projectid') &&
this.resource.projectid !== '') {
params.projectid = this.resource.projectid
}
if (this.isValidValueForKey(this.resource, 'associatednetworkid')) {
params.associatednetworkid = this.resource.associatednetworkid
}
}
api('listPublicIpAddresses', params).then(json => {
const ips = json.listpublicipaddressesresponse.publicipaddress
if (this.arrayHasItems(ips)) {
this.publicIpAddress = ips[0]
}
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: error.response.headers['x-description']
})
}).finally(() => {
this.networkLoading = false
})
},
downloadKubernetesClusterConfig () {
var blob = new Blob([this.clusterConfig], { type: 'text/plain' })
var filename = 'kube.conf'
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename)
} else {
var elem = window.document.createElement('a')
elem.href = window.URL.createObjectURL(blob)
elem.download = filename
document.body.appendChild(elem)
elem.click()
document.body.removeChild(elem)
}
}
}
}
</script>
<style lang="scss" scoped>
.list {
&__item,
&__row {
display: flex;
flex-wrap: wrap;
width: 100%;
}
&__item {
margin-bottom: -20px;
}
&__col {
flex: 1;
margin-right: 20px;
margin-bottom: 20px;
}
&__label {
font-weight: bold;
}
}
.pagination {
margin-top: 20px;
}
.table {
margin-top: 20px;
overflow-y: auto;
}
.action-button {
margin-top: 10px;
text-align: right;
button {
margin-right: 5px;
}
}
</style>

View File

@ -0,0 +1,210 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout">
<a-spin :spinning="loading">
<a-form
:form="form"
@submit="handleSubmit"
layout="vertical">
<a-form-item :label="$t('cks.cluster.size')">
<a-input
v-decorator="['size', {
initialValue: '1',
rules: [{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback('Please enter a valid number')
}
callback()
}
}]
}]"
:placeholder="apiParams.size.description"/>
</a-form-item>
<a-form-item :label="$t('serviceofferingid')">
<a-select
id="offering-selection"
v-decorator="['serviceofferingid', {}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="serviceOfferingLoading"
:placeholder="apiParams.serviceofferingid.description">
<a-select-option v-for="(opt, optIndex) in this.serviceOfferings" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
<a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { api } from '@/api'
export default {
name: 'ScaleKubernetesCluster',
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
originalSize: 1,
serviceOfferings: [],
serviceOfferingLoading: false,
minCpu: 2,
minMemory: 2048,
loading: false
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
this.apiConfig = this.$store.getters.apis.scaleKubernetesCluster || {}
this.apiParams = {}
this.apiConfig.params.forEach(param => {
this.apiParams[param.name] = param
})
},
created () {
},
mounted () {
this.fetchData()
},
methods: {
fetchData () {
this.originalSize = !this.isObjectEmpty(this.resource) ? 1 : this.resource.size
this.fetchKubernetesVersionData()
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
fetchKubernetesVersionData () {
const params = {}
if (!this.isObjectEmpty(this.resource)) {
params.id = this.resource.kubernetesversionid
}
api('listKubernetesSupportedVersions', params).then(json => {
const versionObjs = json.listkubernetessupportedversionsresponse.kubernetessupportedversion
if (this.arrayHasItems(versionObjs) && !this.isObjectEmpty(versionObjs[0])) {
this.minCpu = versionObjs[0].mincpunumber
this.minMemory = versionObjs[0].minmemory
}
}).finally(() => {
this.fetchServiceOfferingData()
})
},
fetchServiceOfferingData () {
this.serviceOfferings = []
const params = {}
this.serviceOfferingLoading = true
api('listServiceOfferings', params).then(json => {
var items = json.listserviceofferingsresponse.serviceoffering
if (items != null) {
for (var i = 0; i < items.length; i++) {
if (items[i].iscustomized === false &&
items[i].cpunumber >= this.minCpu && items[i].memory >= this.minMemory) {
this.serviceOfferings.push(items[i])
}
}
}
}).finally(() => {
this.serviceOfferingLoading = false
if (this.arrayHasItems(this.serviceOfferings)) {
for (var i = 0; i < this.serviceOfferings.length; i++) {
if (this.serviceOfferings[i].id === this.resource.serviceofferingid) {
this.form.setFieldsValue({
serviceofferingid: i
})
break
}
}
}
})
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
this.loading = true
const params = {
id: this.resource.id
}
if (this.isValidValueForKey(values, 'size') && values.size > 0) {
params.kubernetesversionid = this.kubernetesVersions[values.kubernetesversionid].id
}
if (this.isValidValueForKey(values, 'serviceofferingid') && this.arrayHasItems(this.serviceOfferings)) {
params.serviceofferingid = this.serviceOfferings[values.serviceofferingid].id
}
api('scaleKubernetesCluster', params).then(json => {
this.$message.success('Successfully scaled Kubernetes cluster: ' + this.resource.name)
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})
}).finally(() => {
this.$emit('refresh-data')
this.loading = false
this.closeAction()
})
})
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 60vw;
@media (min-width: 500px) {
width: 450px;
}
}
.action-button {
text-align: right;
button {
margin-right: 5px;
}
}
</style>

View File

@ -0,0 +1,186 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout">
<a-spin :spinning="loading">
<a-form
:form="form"
@submit="handleSubmit"
layout="vertical">
<a-form-item :label="$t('kubernetesversionid')">
<a-select
id="version-selection"
v-decorator="['kubernetesversionid', {
rules: [{ required: true }]
}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="kubernetesVersionLoading"
:placeholder="apiParams.kubernetesversionid.description">
<a-select-option v-for="(opt, optIndex) in this.kubernetesVersions" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
<a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { api } from '@/api'
export default {
name: 'UpgradeKubernetesCluster',
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
kubernetesVersions: [],
kubernetesVersionLoading: false,
minCpu: 2,
minMemory: 2048,
loading: false
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
this.apiConfig = this.$store.getters.apis.upgradeKubernetesCluster || {}
this.apiParams = {}
this.apiConfig.params.forEach(param => {
this.apiParams[param.name] = param
})
},
created () {
},
mounted () {
this.fetchData()
},
methods: {
fetchData () {
this.fetchKubernetesVersionData()
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
fetchKubernetesVersionData () {
this.kubernetesVersions = []
const params = {}
if (!this.isObjectEmpty(this.resource)) {
params.minimumkubernetesversionid = this.resource.kubernetesversionid
}
this.kubernetesVersionLoading = true
api('listKubernetesSupportedVersions', params).then(json => {
const versionObjs = json.listkubernetessupportedversionsresponse.kubernetessupportedversion
if (this.arrayHasItems(versionObjs)) {
var clusterVersion = null
for (var j = 0; j < versionObjs.length; j++) {
if (versionObjs[j].id === this.resource.kubernetesversionid) {
clusterVersion = versionObjs[j]
break
}
}
for (var i = 0; i < versionObjs.length; i++) {
if (versionObjs[i].id !== this.resource.kubernetesversionid &&
(clusterVersion == null || (clusterVersion != null && versionObjs[i].semanticversion !== clusterVersion.semanticversion)) &&
versionObjs[i].state === 'Enabled' && versionObjs[i].isostate === 'Ready') {
this.kubernetesVersions.push({
id: versionObjs[i].id,
description: versionObjs[i].name
})
}
}
}
}).finally(() => {
this.kubernetesVersionLoading = false
if (this.arrayHasItems(this.kubernetesVersions)) {
this.form.setFieldsValue({
kubernetesversionid: 0
})
}
})
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
this.loading = true
const params = {
id: this.resource.id
}
if (this.isValidValueForKey(values, 'kubernetesversionid') && this.arrayHasItems(this.kubernetesVersions)) {
params.kubernetesversionid = this.kubernetesVersions[values.kubernetesversionid].id
}
api('upgradeKubernetesCluster', params).then(json => {
this.$message.success('Successfully upgraded Kubernetes cluster: ' + this.resource.name)
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})
}).finally(() => {
this.$emit('refresh-data')
this.loading = false
this.closeAction()
})
})
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 60vw;
@media (min-width: 500px) {
width: 450px;
}
}
.action-button {
text-align: right;
button {
margin-right: 5px;
}
}
</style>

View File

@ -0,0 +1,243 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout">
<a-spin :spinning="loading">
<a-form
:form="form"
@submit="handleSubmit"
layout="vertical">
<a-form-item :label="$t('semanticversion')">
<a-input
v-decorator="['semanticversion', {
rules: [{ required: true, message: 'Please enter Kubernetes semantic version' }]
}]"
:placeholder="apiParams.semanticversion.description"/>
</a-form-item>
<a-form-item :label="$t('name')">
<a-input
v-decorator="['name', {
rules: [{ message: 'Please enter name' }]
}]"
:placeholder="$t('name')"/>
</a-form-item>
<a-form-item :label="$t('zoneid')">
<a-select
id="zone-selection"
v-decorator="['zoneid', {
rules: [
{
validator: (rule, value, callback) => {
if (value && value.length > 1 && value.indexOf(0) !== -1) {
callback('All Zones cannot be combined with any other zone')
}
callback()
}
}
]
}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="zoneLoading"
:placeholder="apiParams.zoneid.description">
<a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('url')">
<a-input
v-decorator="['url', {
rules: [{ required: true, message: 'Please enter binaries ISO URL' }]
}]"
:placeholder="apiParams.url.description" />
</a-form-item>
<a-form-item :label="$t('checksum')">
<a-input
v-decorator="['checksum', {
rules: [{ required: false, message: 'Please enter input' }]
}]"
:placeholder="apiParams.checksum.description" />
</a-form-item>
<a-form-item :label="$t('mincpunumber')">
<a-input
v-decorator="['mincpunumber', {
rules: [{ required: true, message: 'Please enter value' },
{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback('Please enter a valid number')
}
callback()
}
}
]
}]"
:placeholder="apiParams.mincpunumber.description"/>
</a-form-item>
<a-form-item :label="$t('minmemory')">
<a-input
v-decorator="['minmemory', {
rules: [{ required: true, message: 'Please enter value' },
{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback('Please enter a valid number')
}
callback()
}
}
]
}]"
:placeholder="apiParams.minmemory.description"/>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
<a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { api } from '@/api'
export default {
name: 'AddKubernetesSupportedVersion',
props: {},
data () {
return {
zones: [],
zoneLoading: false,
loading: false
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
this.apiConfig = this.$store.getters.apis.addKubernetesSupportedVersion || {}
this.apiParams = {}
this.apiConfig.params.forEach(param => {
this.apiParams[param.name] = param
})
},
created () {
this.zones = [
{
id: null,
name: this.$t('label.all.zone')
}
]
},
mounted () {
this.fetchData()
},
methods: {
fetchData () {
this.fetchZoneData()
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
fetchZoneData () {
const params = {}
params.listAll = true
this.zoneLoading = true
api('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
this.zones = this.zones.concat(listZones)
}).finally(() => {
this.zoneLoading = false
if (this.arrayHasItems(this.zones)) {
this.form.setFieldsValue({
zoneid: 0
})
}
})
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
this.loading = true
const params = {
semanticversion: values.semanticversion,
url: values.url
}
if (this.isValidValueForKey(values, 'name')) {
params.name = values.name
}
if (this.isValidValueForKey(values, 'checksum')) {
params.checksum = values.checksum
}
if (this.isValidValueForKey(values, 'zoneid')) {
params.zoneid = this.zones[values.zoneid].id
}
if (this.isValidValueForKey(values, 'mincpunumber') && values.mincpunumber > 0) {
params.mincpunumber = values.mincpunumber
}
if (this.isValidValueForKey(values, 'minmemory') && values.minmemory > 0) {
params.minmemory = values.minmemory
}
api('addKubernetesSupportedVersion', params).then(json => {
this.$message.success('Successfully added Kubernetes version: ' + values.semanticversion)
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})
}).finally(() => {
this.$emit('refresh-data')
this.loading = false
this.closeAction()
})
})
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 80vw;
@media (min-width: 700px) {
width: 550px;
}
}
.action-button {
text-align: right;
button {
margin-right: 5px;
}
}
</style>

View File

@ -0,0 +1,168 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout">
<a-spin :spinning="loading">
<a-form
:form="form"
@submit="handleSubmit"
layout="vertical">
<a-form-item :label="$t('state')">
<a-select
id="state-selection"
v-decorator="['state', {
rules: [{ required: true }]
}]"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="stateLoading"
:placeholder="apiParams.state.description">
<a-select-option v-for="(opt, optIndex) in this.states" :key="optIndex">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
<a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { api } from '@/api'
export default {
name: 'UpdateKubernetesSupportedVersion',
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
states: [],
stateLoading: false,
loading: false
}
},
beforeCreate () {
this.form = this.$form.createForm(this)
this.apiConfig = this.$store.getters.apis.updateKubernetesSupportedVersion || {}
this.apiParams = {}
this.apiConfig.params.forEach(param => {
this.apiParams[param.name] = param
})
},
created () {
this.states = [
{
id: 'Enabled',
name: this.$t('enabled')
},
{
id: 'Disabled',
name: this.$t('disabled')
}
]
},
mounted () {
this.fetchData()
},
methods: {
fetchData () {
var selectedState = 0
if (!this.isObjectEmpty(this.resource)) {
for (var i = 0; i < this.states.length; ++i) {
if (this.states[i].id === this.resource.state) {
selectedState = i
break
}
}
}
this.form.setFieldsValue({
state: selectedState
})
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
handleSubmit (e) {
e.preventDefault()
this.form.validateFields((err, values) => {
if (err) {
return
}
this.loading = true
const params = {
id: this.resource.id
}
if (this.isValidValueForKey(values, 'state') && this.arrayHasItems(this.states)) {
params.state = this.states[values.state].id
}
api('updateKubernetesSupportedVersion', params).then(json => {
this.$message.success('Successfully updated Kubernetes supported version: ' + this.resource.name)
}).catch(error => {
this.$notification.error({
message: 'Request Failed',
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})
}).finally(() => {
this.$emit('refresh-data')
this.loading = false
this.closeAction()
})
})
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 60vw;
@media (min-width: 500px) {
width: 450px;
}
}
.action-button {
text-align: right;
button {
margin-right: 5px;
}
}
</style>