You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2020/03/25 21:18:49 UTC
[cloudstack-primate] branch master updated: compute: VM deployment
wizard (#146)
This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git
The following commit(s) were added to refs/heads/master by this push:
new 86845be compute: VM deployment wizard (#146)
86845be is described below
commit 86845be4813e9bd50b6278bd0d02deb5c10fd411
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Thu Mar 26 04:18:39 2020 +0700
compute: VM deployment wizard (#146)
A mostly functional vm deployment wizard.
Signed-off-by: Abhishek Kumar <ab...@gmail.com>
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
---
src/locales/en.json | 2 +-
src/utils/device.js | 7 +-
src/views/AutogenView.vue | 2 +-
src/views/compute/DeployVM.vue | 806 +++++++++++++++++----
src/views/compute/KubernetesServiceTab.vue | 2 +-
.../compute/wizard/AffinityGroupSelection.vue | 51 +-
src/views/compute/wizard/ComputeSelection.vue | 62 +-
src/views/compute/wizard/DiskOfferingSelection.vue | 108 ++-
src/views/compute/wizard/DiskSizeSelection.vue | 17 +-
src/views/compute/wizard/NetworkConfiguration.vue | 65 +-
src/views/compute/wizard/NetworkSelection.vue | 76 +-
src/views/compute/wizard/NetworksCreation.vue | 150 ----
src/views/compute/wizard/SshKeyPairSelection.vue | 80 +-
src/views/compute/wizard/TemplateIsoRadioGroup.vue | 136 +++-
src/views/compute/wizard/TemplateIsoSelection.vue | 161 ++--
src/views/iam/SSLCertificateTab.vue | 2 +-
src/views/image/IsoZones.vue | 2 +-
src/views/image/TemplateZones.vue | 2 +-
src/views/network/VpcTab.vue | 2 +-
19 files changed, 1242 insertions(+), 491 deletions(-)
diff --git a/src/locales/en.json b/src/locales/en.json
index 6511211..d4ddbff 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -1103,7 +1103,7 @@
"sshKeyPairs": "SSH keypairs",
"wednesday": "Wednesday",
"noselect": "No thanks",
-"groupname": "Add to group",
+"group": "Group",
"keyboard": "Keyboard language",
"userdata": "Userdata",
"label.back": "Back",
diff --git a/src/utils/device.js b/src/utils/device.js
index 731270d..ce6deab 100644
--- a/src/utils/device.js
+++ b/src/utils/device.js
@@ -42,9 +42,8 @@ export const deviceEnquire = function (callback) {
}
}
- // screen and (max-width: 1087.99px)
enquireJs
- .register('screen and (max-width: 576px)', matchMobile)
- .register('screen and (min-width: 576px) and (max-width: 1280px)', matchTablet)
- .register('screen and (min-width: 1281px)', matchDesktop)
+ .register('screen and (max-width: 800px)', matchMobile)
+ .register('screen and (min-width: 800px) and (max-width: 1366px)', matchTablet)
+ .register('screen and (min-width: 1367px)', matchDesktop)
}
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 8887a5e..871c380 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -235,7 +235,7 @@
:pageSize="pageSize"
:total="itemCount"
:showTotal="total => `Total ${total} items`"
- :pageSizeOptions="['10', '20', '40', '80', '100']"
+ :pageSizeOptions="['10', '20', '40', '80', '100', '500']"
@change="changePage"
@showSizeChange="changePageSize"
showSizeChanger
diff --git a/src/views/compute/DeployVM.vue b/src/views/compute/DeployVM.vue
index eb0a5f7..f1a4c9e 100644
--- a/src/views/compute/DeployVM.vue
+++ b/src/views/compute/DeployVM.vue
@@ -25,131 +25,216 @@
@submit="handleSubmit"
layout="vertical"
>
- <a-collapse
- :accordion="false"
- :bordered="false"
- defaultActiveKey="basic"
- >
- <a-collapse-panel :header="this.$t('BasicSetup')" key="basic">
- <a-form-item :label="this.$t('name')">
- <a-input
- v-decorator="['name']"
- :placeholder="this.$t('vm.name.description')"
- />
- </a-form-item>
-
- <a-form-item :label="this.$t('zoneid')">
- <a-select
- v-decorator="['zoneid', {
- rules: [{ required: true, message: 'Please select option' }]
- }]"
- :placeholder="this.$t('vm.zone.description')"
- :options="zoneSelectOptions"
- @change="onSelectZoneId"
- ></a-select>
- </a-form-item>
- </a-collapse-panel>
-
- <a-collapse-panel :header="this.$t('templateIso')" key="templates-isos">
- <a-collapse
- :accordion="true"
- defaultActiveKey="templates"
- @change="onTemplatesIsosCollapseChange"
- >
- <a-collapse-panel :header="this.$t('Templates')" key="templates">
- <template-iso-selection
- input-decorator="templateid"
- :items="options.templates"
- ></template-iso-selection>
+ <a-steps direction="vertical" size="small">
+ <a-step :title="this.$t('details')" status="process">
+ <template slot="description">
+ <div style="margin-top: 15px">
+ <a-form-item :label="this.$t('name')">
+ <a-input
+ v-decorator="['name']"
+ />
+ </a-form-item>
+ <a-form-item :label="this.$t('zoneid')">
+ <a-select
+ v-decorator="['zoneid', {
+ rules: [{ required: true, message: 'Please select option' }]
+ }]"
+ :options="zoneSelectOptions"
+ @change="onSelectZoneId"
+ :loading="loading.zones"
+ ></a-select>
+ </a-form-item>
+ <a-form-item :label="this.$t('podId')">
+ <a-select
+ v-decorator="['podid']"
+ :options="podSelectOptions"
+ :loading="loading.pods"
+ ></a-select>
+ </a-form-item>
+ <a-form-item :label="this.$t('clusterid')">
+ <a-select
+ v-decorator="['clusterid']"
+ :options="clusterSelectOptions"
+ :loading="loading.clusters"
+ ></a-select>
+ </a-form-item>
+ <a-form-item :label="this.$t('hostId')">
+ <a-select
+ v-decorator="['hostid']"
+ :options="hostSelectOptions"
+ :loading="loading.hosts"
+ ></a-select>
+ </a-form-item>
+ <a-form-item :label="this.$t('group')">
+ <a-select
+ v-decorator="['group']"
+ :options="groupsSelectOptions"
+ :loading="loading.groups"
+ ></a-select>
+ </a-form-item>
+ <a-form-item :label="this.$t('keyboard')">
+ <a-select
+ v-decorator="['keyboard']"
+ :options="keyboardSelectOptions"
+ ></a-select>
+ </a-form-item>
+ <a-form-item :label="this.$t('userdata')">
+ <a-textarea
+ v-decorator="['userdata']">
+ </a-textarea>
+ </a-form-item>
+ </div>
+ </template>
+ </a-step>
+ <a-step
+ :title="this.$t('templateIso')"
+ :status="zoneSelected ? 'process' : 'wait'">
+ <template slot="description">
+ <div v-if="zoneSelected" style="margin-top: 15px">
+ <a-card
+ :tabList="tabList"
+ :activeTabKey="tabKey"
+ @tabChange="key => onTabChange(key, 'tabKey')">
+ <p v-if="tabKey === 'templateid'">
+ <template-iso-selection
+ input-decorator="templateid"
+ :items="options.templates"
+ :selected="tabKey"
+ :loading="loading.templates"
+ @update-template-iso="updateFieldValue"
+ ></template-iso-selection>
+ <disk-size-selection
+ input-decorator="rootdisksize"
+ @update-disk-size="updateFieldValue"/>
+ </p>
+ <p v-else>
+ <template-iso-selection
+ input-decorator="isoid"
+ :items="options.isos"
+ :selected="tabKey"
+ :loading="loading.isos"
+ @update-template-iso="updateFieldValue"
+ ></template-iso-selection>
+ </p>
+ </a-card>
+ <a-form-item class="form-item-hidden">
+ <a-input v-decorator="['templateid']"/>
+ </a-form-item>
+ <a-form-item class="form-item-hidden">
+ <a-input v-decorator="['isoid']"/>
+ </a-form-item>
+ <a-form-item class="form-item-hidden">
+ <a-input v-decorator="['rootdisksize']"/>
+ </a-form-item>
+ </div>
+ </template>
+ </a-step>
+ <a-step
+ :title="this.$t('serviceOfferingId')"
+ :status="zoneSelected ? 'process' : 'wait'">
+ <template slot="description">
+ <div v-if="zoneSelected">
+ <compute-selection
+ :compute-items="options.serviceOfferings"
+ :value="serviceOffering ? serviceOffering.id : ''"
+ :loading="loading.serviceOfferings"
+ @select-compute-item="($event) => updateComputeOffering($event)"
+ @handle-search-filter="($event) => handleSearchFilter('serviceOfferings', $event)"
+ ></compute-selection>
+ </div>
+ </template>
+ </a-step>
+ <a-step
+ :title="this.$t('diskofferingid')"
+ :status="zoneSelected ? 'process' : 'wait'">
+ <template slot="description">
+ <div v-if="zoneSelected">
+ <disk-offering-selection
+ :items="options.diskOfferings"
+ :value="diskOffering ? diskOffering.id : ''"
+ :loading="loading.diskOfferings"
+ @select-disk-offering-item="($event) => updateDiskOffering($event)"
+ @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
+ ></disk-offering-selection>
<disk-size-selection
- input-decorator="rootdisksize"
- ></disk-size-selection>
- </a-collapse-panel>
-
- <a-collapse-panel :header="this.$t('ISOs')" key="isos">
- <template-iso-selection
- input-decorator="isoid"
- :items="options.isos"
- ></template-iso-selection>
- </a-collapse-panel>
- </a-collapse>
- </a-collapse-panel>
-
- <a-collapse-panel :header="this.$t('serviceOfferingId')" key="compute">
- <compute-selection
- :compute-items="options.serviceOfferings"
- :value="serviceOffering ? serviceOffering.id : ''"
- @select-compute-item="($event) => updateComputeOffering($event)"
- ></compute-selection>
- </a-collapse-panel>
-
- <a-collapse-panel :header="this.$t('diskOfferingId')" key="disk">
- <disk-offering-selection
- :items="options.diskOfferings"
- :value="diskOffering ? diskOffering.id : ''"
- @select-disk-offering-item="($event) => updateDiskOffering($event)"
- ></disk-offering-selection>
- <disk-size-selection
- v-if="diskOffering && diskOffering.iscustomized"
- input-decorator="size"
- ></disk-size-selection>
- </a-collapse-panel>
-
- <a-collapse-panel :header="this.$t('Affinity Groups')" key="affinity">
- <affinity-group-selection
- :items="options.affinityGroups"
- :value="affinityGroupIds"
- @select-affinity-group-item="($event) => updateAffinityGroups($event)"
- ></affinity-group-selection>
- </a-collapse-panel>
-
- <a-collapse-panel :header="this.$t('networks')" key="networks">
- <a-collapse
- :accordion="false"
- >
- <a-collapse-panel
- :header="$t('existingNetworks')"
- >
+ v-if="diskOffering && diskOffering.iscustomized"
+ input-decorator="size"
+ @update-disk-size="updateFieldValue" />
+ <a-form-item class="form-item-hidden">
+ <a-input v-decorator="['size']"/>
+ </a-form-item>
+ </div>
+ </template>
+ </a-step>
+ <a-step
+ :title="this.$t('Affinity Groups')"
+ :status="zoneSelected ? 'process' : 'wait'">
+ <template slot="description">
+ <div v-if="zoneSelected">
+ <affinity-group-selection
+ :items="options.affinityGroups"
+ :value="affinityGroupIds"
+ :loading="loading.affinityGroups"
+ @select-affinity-group-item="($event) => updateAffinityGroups($event)"
+ @handle-search-filter="($event) => handleSearchFilter('affinityGroups', $event)"
+ ></affinity-group-selection>
+ </div>
+ </template>
+ </a-step>
+ <a-step
+ :title="this.$t('networks')"
+ :status="zoneSelected ? 'process' : 'wait'">
+ <template slot="description">
+ <div v-if="zoneSelected">
<network-selection
:items="options.networks"
:value="networkOfferingIds"
+ :loading="loading.networks"
+ :zoneId="zoneId"
@select-network-item="($event) => updateNetworks($event)"
+ @handle-search-filter="($event) => handleSearchFilter('networks', $event)"
></network-selection>
- </a-collapse-panel>
-
- <a-collapse-panel
- :header="$t('addNewNetworks')"
- >
- <network-creation></network-creation>
- </a-collapse-panel>
- </a-collapse>
-
- <network-configuration
- v-if="networks.length > 0"
- :items="networks"
- ></network-configuration>
- </a-collapse-panel>
-
- <a-collapse-panel :header="this.$t('sshKeyPairs')" key="sshKeyPairs">
- <ssh-key-pair-selection
- :items="options.sshKeyPairs"
- :value="sshKeyPair ? sshKeyPair.name : ''"
- @select-ssh-key-pair-item="($event) => updateSshKeyPairs($event)"
- ></ssh-key-pair-selection>
- </a-collapse-panel>
- </a-collapse>
-
+ <network-configuration
+ v-if="networks.length > 0"
+ :items="networks"
+ @update-network-config="($event) => updateNetworkConfig($event)"
+ @select-default-network-item="($event) => updateDefaultNetworks($event)"
+ ></network-configuration>
+ </div>
+ </template>
+ </a-step>
+ <a-step
+ :title="this.$t('sshKeyPairs')"
+ :status="zoneSelected ? 'process' : 'wait'">
+ <template slot="description">
+ <div v-if="zoneSelected">
+ <ssh-key-pair-selection
+ :items="options.sshKeyPairs"
+ :value="sshKeyPair ? sshKeyPair.name : ''"
+ :loading="loading.sshKeyPairs"
+ @select-ssh-key-pair-item="($event) => updateSshKeyPairs($event)"
+ @handle-search-filter="($event) => handleSearchFilter('sshKeyPairs', $event)"
+ />
+ </div>
+ </template>
+ </a-step>
+ </a-steps>
<div class="card-footer">
<!-- ToDo extract as component -->
- <a-button @click="() => this.$router.back()">{{ this.$t('cancel') }}</a-button>
- <a-button type="primary" @click="handleSubmit">{{ this.$t('submit') }}</a-button>
+ <a-button @click="() => this.$router.back()" :loading="loading.deploy">
+ {{ this.$t('cancel') }}
+ </a-button>
+ <a-button type="primary" @click="handleSubmit" :loading="loading.deploy">
+ <a-icon type="rocket" />
+ {{ this.$t('Launch VM') }}
+ </a-button>
</div>
</a-form>
</a-card>
</a-col>
<a-col :md="24" :lg="7" v-if="!isMobile()">
<a-affix :offsetTop="75">
- <info-card :resource="vm" :title="this.$t('yourInstance')">
+ <info-card class="vm-info-card" :resource="vm" :title="this.$t('yourInstance')">
<!-- ToDo: Refactor this, maybe move everything to the info-card component -->
<div slot="details" v-if="diskSize" style="margin-bottom: 12px;">
<a-icon type="hdd"></a-icon>
@@ -183,14 +268,12 @@ import TemplateIsoSelection from '@views/compute/wizard/TemplateIsoSelection'
import AffinityGroupSelection from '@views/compute/wizard/AffinityGroupSelection'
import NetworkSelection from '@views/compute/wizard/NetworkSelection'
import NetworkConfiguration from '@views/compute/wizard/NetworkConfiguration'
-import NetworkCreation from '@views/compute/wizard/NetworksCreation'
import SshKeyPairSelection from '@views/compute/wizard/SshKeyPairSelection'
export default {
name: 'Wizard',
components: {
SshKeyPairSelection,
- NetworkCreation,
NetworkConfiguration,
NetworkSelection,
AffinityGroupSelection,
@@ -208,6 +291,8 @@ export default {
mixins: [mixin, mixinDevice],
data () {
return {
+ zoneId: '',
+ zoneSelected: false,
vm: {},
options: {
templates: [],
@@ -217,7 +302,27 @@ export default {
zones: [],
affinityGroups: [],
networks: [],
- sshKeyPairs: []
+ sshKeyPairs: [],
+ pods: [],
+ clusters: [],
+ hosts: [],
+ groups: [],
+ keyboards: []
+ },
+ loading: {
+ deploy: false,
+ templates: false,
+ isos: false,
+ serviceOfferings: false,
+ diskOfferings: false,
+ affinityGroups: false,
+ networks: false,
+ sshKeyPairs: false,
+ zones: false,
+ pods: false,
+ clusters: false,
+ hosts: false,
+ groups: false
},
instanceConfig: [],
template: {},
@@ -226,13 +331,44 @@ export default {
diskOffering: {},
affinityGroups: [],
networks: [],
+ networksAdd: [],
zone: {},
sshKeyPair: {},
+ templateFilter: [
+ 'featured',
+ 'community',
+ 'selfexecutable',
+ 'sharedexecutable'
+ ],
isoFilter: [
- 'executable',
+ 'featured',
+ 'community',
'selfexecutable',
'sharedexecutable'
- ]
+ ],
+ steps: {
+ BASIC: 0,
+ TEMPLATE_ISO: 1,
+ COMPUTE: 2,
+ DISK_OFFERING: 3,
+ AFFINITY_GROUP: 4,
+ NETWORK: 5,
+ SSH_KEY_PAIR: 6
+ },
+ initDataConfig: {},
+ defaultNetwork: '',
+ networkConfig: [],
+ tabList: [
+ {
+ key: 'templateid',
+ tab: this.$t('Templates')
+ },
+ {
+ key: 'isoid',
+ tab: this.$t('ISOs')
+ }
+ ],
+ tabKey: 'templateid'
}
},
computed: {
@@ -255,35 +391,78 @@ export default {
},
params () {
return {
- templates: {
- list: 'listTemplates',
+ serviceOfferings: {
+ list: 'listServiceOfferings',
options: {
- templatefilter: 'executable',
- zoneid: _.get(this.zone, 'id')
+ zoneid: _.get(this.zone, 'id'),
+ issystem: false,
+ page: 1,
+ pageSize: 10,
+ keyword: undefined
}
},
- serviceOfferings: {
- list: 'listServiceOfferings'
- },
diskOfferings: {
- list: 'listDiskOfferings'
+ list: 'listDiskOfferings',
+ options: {
+ zoneid: _.get(this.zone, 'id'),
+ page: 1,
+ pageSize: 10,
+ keyword: undefined
+ }
},
zones: {
list: 'listZones'
},
affinityGroups: {
- list: 'listAffinityGroups'
+ list: 'listAffinityGroups',
+ options: {
+ page: 1,
+ pageSize: 10,
+ keyword: undefined
+ }
},
sshKeyPairs: {
- list: 'listSSHKeyPairs'
+ list: 'listSSHKeyPairs',
+ options: {
+ zoneid: _.get(this.zone, 'id'),
+ page: 1,
+ pageSize: 10,
+ keyword: undefined
+ }
},
networks: {
list: 'listNetworks',
options: {
zoneid: _.get(this.zone, 'id'),
canusefordeploy: true,
- projectid: store.getters.project.id
+ projectid: store.getters.project.id,
+ page: 1,
+ pageSize: 10,
+ keyword: undefined
+ }
+ },
+ pods: {
+ list: 'listPods',
+ options: {
+ zoneid: _.get(this.zone, 'id')
}
+ },
+ clusters: {
+ list: 'listClusters',
+ options: {
+ zoneid: _.get(this.zone, 'id')
+ }
+ },
+ hosts: {
+ list: 'listHosts',
+ options: {
+ zoneid: _.get(this.zone, 'id'),
+ state: 'Up',
+ type: 'Routing'
+ }
+ },
+ groups: {
+ list: 'listInstanceGroups'
}
}
},
@@ -297,9 +476,54 @@ export default {
value: zone.id
}
})
+ },
+ podSelectOptions () {
+ return this.options.pods.map((pod) => {
+ return {
+ label: pod.name,
+ value: pod.id
+ }
+ })
+ },
+ clusterSelectOptions () {
+ return this.options.clusters.map((cluster) => {
+ return {
+ label: cluster.name,
+ value: cluster.id
+ }
+ })
+ },
+ hostSelectOptions () {
+ return this.options.hosts.map((host) => {
+ return {
+ label: host.name,
+ value: host.id
+ }
+ })
+ },
+ keyboardSelectOptions () {
+ return this.options.keyboards.map((keyboard) => {
+ return {
+ label: this.$t(keyboard.description),
+ value: keyboard.id
+ }
+ })
+ },
+ groupsSelectOptions () {
+ return this.options.groups.map((group) => {
+ return {
+ label: group.name,
+ value: group.id
+ }
+ })
}
},
watch: {
+ '$route' (to, from) {
+ if (to.name === 'deployVirtualMachine') {
+ this.resetData()
+ }
+ },
instanceConfig (instanceConfig) {
this.template = _.find(this.options.templates, (option) => option.id === instanceConfig.templateid)
this.iso = _.find(this.options.isos, (option) => option.id === instanceConfig.isoid)
@@ -363,26 +587,99 @@ export default {
this.vm = this.instanceConfig
}
})
- this.form.getFieldDecorator('computeofferingid', { initialValue: [], preserve: true })
- this.form.getFieldDecorator('diskofferingid', { initialValue: [], preserve: true })
+ this.form.getFieldDecorator('computeofferingid', { initialValue: undefined, preserve: true })
+ this.form.getFieldDecorator('diskofferingid', { initialValue: undefined, preserve: true })
this.form.getFieldDecorator('affinitygroupids', { initialValue: [], preserve: true })
- this.form.getFieldDecorator('isoid', { initialValue: [], preserve: true })
+ this.form.getFieldDecorator('isoid', { initialValue: undefined, preserve: true })
this.form.getFieldDecorator('networkids', { initialValue: [], preserve: true })
- this.form.getFieldDecorator('keypair', { initialValue: [], preserve: true })
+ this.form.getFieldDecorator('keypair', { initialValue: undefined, preserve: true })
+ this.apiParams = {}
+ this.apiDeployVirtualMachine = this.$store.getters.apis.deployVirtualMachine || {}
+ this.apiDeployVirtualMachine.params.forEach(param => {
+ this.apiParams[param.name] = param
+ })
},
created () {
- _.each(this.params, this.fetchOptions)
- Vue.nextTick().then(() => {
- this.instanceConfig = this.form.getFieldsValue() // ToDo: maybe initialize with some other defaults
- })
+ this.fetchData()
},
methods: {
+ fetchData () {
+ this.fetchOptions(this.params.zones, 'zones')
+ this.fetchOptions(this.params.pods, 'pods')
+ this.fetchOptions(this.params.clusters, 'clusters')
+ this.fetchOptions(this.params.hosts, 'hosts')
+ this.fetchOptions(this.params.groups, 'groups')
+ this.fetchKeyboard()
+ Vue.nextTick().then(() => {
+ this.instanceConfig = this.form.getFieldsValue() // ToDo: maybe initialize with some other defaults
+ })
+ },
+ fetchKeyboard () {
+ const keyboardType = []
+ keyboardType.push({
+ id: '',
+ description: ''
+ })
+ keyboardType.push({
+ id: 'us',
+ description: 'label.standard.us.keyboard'
+ })
+ keyboardType.push({
+ id: 'uk',
+ description: 'label.uk.keyboard'
+ })
+ keyboardType.push({
+ id: 'fr',
+ description: 'label.french.azerty.keyboard'
+ })
+ keyboardType.push({
+ id: 'jp',
+ description: 'label.japanese.keyboard'
+ })
+ keyboardType.push({
+ id: 'sc',
+ description: 'label.simplified.chinese.keyboard'
+ })
+
+ this.$set(this.options, 'keyboards', keyboardType)
+ },
+ resetData () {
+ this.vm = {}
+ this.zoneSelected = false
+ this.form.resetFields()
+ this.fetchData()
+ },
+ updateFieldValue (name, value) {
+ if (name === 'templateid') {
+ this.tabKey = 'templateid'
+ this.form.setFieldsValue({
+ templateid: value,
+ isoid: undefined
+ })
+ } else if (name === 'isoid') {
+ this.tabKey = 'isoid'
+ this.form.setFieldsValue({
+ isoid: value,
+ templateid: undefined
+ })
+ } else {
+ this.form.setFieldsValue({
+ [name]: value
+ })
+ }
+ },
updateComputeOffering (id) {
this.form.setFieldsValue({
computeofferingid: id
})
},
updateDiskOffering (id) {
+ if (id === '0') {
+ this.form.setFieldsValue({
+ diskofferingid: undefined
+ })
+ return
+ }
this.form.setFieldsValue({
diskofferingid: id
})
@@ -397,7 +694,19 @@ export default {
networkids: ids
})
},
+ updateDefaultNetworks (id) {
+ this.defaultNetwork = id
+ },
+ updateNetworkConfig (networks) {
+ this.networkConfig = networks
+ },
updateSshKeyPairs (name) {
+ if (name === this.$t('noselect')) {
+ this.form.setFieldsValue({
+ keypair: undefined
+ })
+ return
+ }
this.form.setFieldsValue({
keypair: name
})
@@ -405,10 +714,103 @@ export default {
getText (option) {
return _.get(option, 'displaytext', _.get(option, 'name'))
},
- handleSubmit () {
+ handleSubmit (e) {
console.log('wizard submit')
+ e.preventDefault()
+ this.form.validateFields((err, values) => {
+ if (err) {
+ return
+ }
+ const deployVmData = {}
+ // step 1 : select zone
+ deployVmData.zoneid = values.zoneid
+ deployVmData.podid = values.podid
+ deployVmData.clusterid = values.clusterid
+ deployVmData.hostid = values.hostid
+ deployVmData.group = values.group
+ deployVmData.keyboard = values.keyboard
+ if (values.keyboard && values.keyboard.length > 0) {
+ deployVmData.userdata = encodeURIComponent(btoa(this.sanitizeReverse(values.keyboard)))
+ }
+ // step 2: select template/iso
+ if (this.tabKey === 'templateid') {
+ deployVmData.templateid = values.templateid
+ } else {
+ deployVmData.templateid = values.isoid
+ }
+ if (values.rootdisksize && values.rootdisksize > 0) {
+ deployVmData.rootdisksize = values.rootdisksize
+ }
+ // step 3: select service offering
+ deployVmData.serviceofferingid = values.computeofferingid
+ // step 4: select disk offering
+ deployVmData.diskofferingid = values.diskofferingid
+ if (values.size) {
+ deployVmData.size = values.size
+ }
+ // step 5: select an affinity group
+ deployVmData.affinitygroupids = values.affinitygroupids.join(',')
+ // step 6: select network
+ if (values.networkids && values.networkids.length > 0) {
+ for (let i = 0; i < values.networkids.length; i++) {
+ deployVmData['iptonetworklist[' + i + '].networkid'] = values.networkids[i]
+ if (this.networkConfig.length > 0) {
+ const networkConfig = this.networkConfig.filter((item) => item.key === values.networkids[i])
+ if (networkConfig && networkConfig.length > 0) {
+ deployVmData['iptonetworklist[' + i + '].ip'] = networkConfig[0].ipAddress ? networkConfig[0].ipAddress : undefined
+ deployVmData['iptonetworklist[' + i + '].mac'] = networkConfig[0].macAddress ? networkConfig[0].macAddress : undefined
+ }
+ }
+ }
+ }
+ // step 7: select ssh key pair
+ deployVmData.keypair = values.keypair
+ deployVmData.name = values.name
+ deployVmData.displayname = values.name
+ const title = this.$t('Launch Virtual Machine')
+ const description = deployVmData.name ? deployVmData.name : values.zoneid
+ this.loading.deploy = true
+ api('deployVirtualMachine', deployVmData).then(response => {
+ const jobId = response.deployvirtualmachineresponse.jobid
+ if (jobId) {
+ this.$pollJob({
+ jobId,
+ successMethod: result => {
+ let successDescription = ''
+ if (result.jobresult.virtualmachine.name) {
+ successDescription = result.jobresult.virtualmachine.name
+ } else {
+ successDescription = result.jobresult.virtualmachine.id
+ }
+ this.$store.dispatch('AddAsyncJob', {
+ title: title,
+ jobid: jobId,
+ description: successDescription,
+ status: 'progress'
+ })
+ },
+ loadingMessage: `${title} in progress for ${description}`,
+ catchMessage: 'Error encountered while fetching async job result'
+ })
+ }
+ this.$router.back()
+ }).catch(error => {
+ this.$notification.error({
+ message: 'Request Failed',
+ description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
+ })
+ }).finally(() => {
+ this.loading.deploy = false
+ })
+ })
},
- fetchOptions (param, name) {
+ fetchOptions (param, name, exclude) {
+ if (exclude && exclude.length > 0) {
+ if (exclude.includes(name)) {
+ return
+ }
+ }
+ this.loading[name] = true
param.loading = true
param.opts = []
const options = param.options || {}
@@ -416,6 +818,11 @@ export default {
api(param.list, options).then((response) => {
param.loading = false
_.map(response, (responseItem, responseKey) => {
+ if (Object.keys(responseItem).length === 0) {
+ this.options[name] = []
+ this.$forceUpdate()
+ return
+ }
if (!responseKey.includes('response')) {
return
}
@@ -431,41 +838,108 @@ export default {
}).catch(function (error) {
console.log(error.stack)
param.loading = false
+ }).finally(() => {
+ this.loading[name] = false
+ })
+ },
+ fetchTemplates (templateFilter) {
+ return new Promise((resolve, reject) => {
+ api('listTemplates', {
+ zoneid: _.get(this.zone, 'id'),
+ templatefilter: templateFilter
+ }).then((response) => {
+ resolve(response)
+ }).catch((reason) => {
+ // ToDo: Handle errors
+ reject(reason)
+ })
})
},
fetchIsos (isoFilter) {
- api('listIsos', {
- zoneid: _.get(this.zone, 'id'),
- isofilter: isoFilter,
- bootable: true
- }).then((response) => {
- const concatedIsos = _.concat(this.options.isos, _.get(response, 'listisosresponse.iso', []))
- this.options.isos = _.uniqWith(concatedIsos, _.isEqual)
- this.$forceUpdate()
+ return new Promise((resolve, reject) => {
+ api('listIsos', {
+ zoneid: _.get(this.zone, 'id'),
+ isofilter: isoFilter,
+ bootable: true
+ }).then((response) => {
+ resolve(response)
+ }).catch((reason) => {
+ // ToDo: Handle errors
+ reject(reason)
+ })
+ })
+ },
+ fetchAllTemplates () {
+ const promises = []
+ this.options.templates = []
+ this.loading.templates = true
+ this.templateFilter.forEach((filter) => {
+ promises.push(this.fetchTemplates(filter))
+ })
+ Promise.all(promises).then(response => {
+ response.forEach((resItem) => {
+ const concatTemplates = _.concat(this.options.templates, _.get(resItem, 'listtemplatesresponse.template', []))
+ this.options.templates = _.uniqWith(concatTemplates, _.isEqual)
+ this.$forceUpdate()
+ })
}).catch((reason) => {
- // ToDo: Handle errors
console.log(reason)
+ }).finally(() => {
+ this.loading.templates = false
})
},
fetchAllIsos () {
+ const promises = []
this.options.isos = []
+ this.loading.isos = true
this.isoFilter.forEach((filter) => {
- this.fetchIsos(filter)
+ promises.push(this.fetchIsos(filter))
+ })
+ Promise.all(promises).then(response => {
+ response.forEach((resItem) => {
+ const concatedIsos = _.concat(this.options.isos, _.get(resItem, 'listisosresponse.iso', []))
+ this.options.isos = _.uniqWith(concatedIsos, _.isEqual)
+ this.$forceUpdate()
+ })
+ }).catch((reason) => {
+ console.log(reason)
+ }).finally(() => {
+ this.loading.isos = false
})
},
- onTemplatesIsosCollapseChange (key) {
- if (key === 'isos' && this.options.isos.length === 0) {
+ onSelectZoneId (value) {
+ this.zoneId = value
+ this.zoneSelected = true
+ this.form.setFieldsValue({
+ clusterid: undefined,
+ podid: undefined,
+ hostid: undefined,
+ templateid: undefined,
+ isoid: undefined
+ })
+ this.tabKey = 'templateid'
+ _.each(this.params, (param, name) => {
+ this.fetchOptions(param, name, ['zones', 'groups'])
+ })
+ this.fetchAllTemplates()
+ },
+ handleSearchFilter (name, options) {
+ this.params[name].options = { ...this.params[name].options, ...options }
+ this.fetchOptions(this.params[name], name)
+ },
+ onTabChange (key, type) {
+ this[type] = key
+ if (key === 'isoid') {
this.fetchAllIsos()
}
},
- onSelectZoneId () {
- this.$nextTick(() => {
- if (this.options.isos.length !== 0) {
- this.fetchAllIsos()
- }
- this.fetchOptions(this.params.templates, 'templates')
- this.fetchOptions(this.params.networks, 'networks')
- })
+ sanitizeReverse (value) {
+ const reversedValue = value
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+
+ return reversedValue
}
}
}
@@ -505,4 +979,22 @@ export default {
border-radius: @border-radius-base !important;
margin: 0 0 1.2rem;
}
+
+ .vm-info-card {
+ .resource-detail-item__label {
+ font-weight: normal;
+ }
+
+ .resource-detail-item__details, .resource-detail-item {
+ a {
+ color: rgba(0, 0, 0, 0.65);
+ cursor: default;
+ pointer-events: none;
+ }
+ }
+ }
+
+ .form-item-hidden {
+ display: none;
+ }
</style>
diff --git a/src/views/compute/KubernetesServiceTab.vue b/src/views/compute/KubernetesServiceTab.vue
index d71d5a6..daa7851 100644
--- a/src/views/compute/KubernetesServiceTab.vue
+++ b/src/views/compute/KubernetesServiceTab.vue
@@ -19,7 +19,7 @@
<a-spin :spinning="networkLoading">
<a-tabs
:activeKey="currentTab"
- :tabPosition="device === 'tablet' || device === 'mobile' ? 'top' : 'left'"
+ :tabPosition="device === 'mobile' ? 'top' : 'left'"
:animated="false"
@change="handleChangeTab">
<a-tab-pane :tab="$t('details')" key="details">
diff --git a/src/views/compute/wizard/AffinityGroupSelection.vue b/src/views/compute/wizard/AffinityGroupSelection.vue
index c13cbcc..be14d6f 100644
--- a/src/views/compute/wizard/AffinityGroupSelection.vue
+++ b/src/views/compute/wizard/AffinityGroupSelection.vue
@@ -16,15 +16,24 @@
// under the License.
<template>
- <a-table
- :columns="columns"
- :dataSource="items"
- :rowKey="record => record.id"
- :pagination="{showSizeChanger: true}"
- :rowSelection="rowSelection"
- size="middle"
- >
- </a-table>
+ <div>
+ <a-input-search
+ style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+ placeholder="Search"
+ v-model="filter"
+ @search="handleSearch" />
+ <a-table
+ :loading="loading"
+ :columns="columns"
+ :dataSource="items"
+ :rowKey="record => record.id"
+ :pagination="{showSizeChanger: true}"
+ :rowSelection="rowSelection"
+ size="middle"
+ :scroll="{ y: 225 }"
+ >
+ </a-table>
+ </div>
</template>
<script>
@@ -40,10 +49,15 @@ export default {
value: {
type: Array,
default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
}
},
data () {
return {
+ filter: '',
columns: [
{
dataIndex: 'name',
@@ -60,6 +74,13 @@ export default {
}
},
computed: {
+ options () {
+ return {
+ page: 1,
+ pageSize: 10,
+ keyword: ''
+ }
+ },
rowSelection () {
return {
type: 'checkbox',
@@ -76,6 +97,18 @@ export default {
this.selectedRowKeys = newValue
}
}
+ },
+ methods: {
+ handleSearch (value) {
+ this.filter = value
+ this.options.keyword = this.filter
+ this.$emit('handle-search-filter', this.options)
+ },
+ handleTableChange (pagination) {
+ this.options.page = pagination.current
+ this.options.pageSize = pagination.pageSize
+ this.$emit('handle-search-filter', this.options)
+ }
}
}
</script>
diff --git a/src/views/compute/wizard/ComputeSelection.vue b/src/views/compute/wizard/ComputeSelection.vue
index dfe4e49..e44d17a 100644
--- a/src/views/compute/wizard/ComputeSelection.vue
+++ b/src/views/compute/wizard/ComputeSelection.vue
@@ -16,16 +16,26 @@
// under the License.
<template>
- <a-table
- :columns="columns"
- :dataSource="tableSource"
- :pagination="{showSizeChanger: true}"
- :rowSelection="rowSelection"
- size="middle"
- >
- <span slot="cpuTitle"><a-icon type="appstore" /> {{ $t('cpu') }}</span>
- <span slot="ramTitle"><a-icon type="bulb" /> {{ $t('memory') }}</span>
- </a-table>
+ <div>
+ <a-input-search
+ style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+ placeholder="Search"
+ v-model="filter"
+ @search="handleSearch" />
+ <a-table
+ :columns="columns"
+ :dataSource="tableSource"
+ :pagination="{showSizeChanger: true}"
+ :rowSelection="rowSelection"
+ :loading="loading"
+ size="middle"
+ @change="handleTableChange"
+ :scroll="{ y: 225 }"
+ >
+ <span slot="cpuTitle"><a-icon type="appstore" /> {{ $t('cpu') }}</span>
+ <span slot="ramTitle"><a-icon type="bulb" /> {{ $t('memory') }}</span>
+ </a-table>
+ </div>
</template>
<script>
@@ -39,10 +49,15 @@ export default {
value: {
type: String,
default: ''
+ },
+ loading: {
+ type: Boolean,
+ default: false
}
},
data () {
return {
+ filter: '',
columns: [
{
dataIndex: 'name',
@@ -64,6 +79,13 @@ export default {
}
},
computed: {
+ options () {
+ return {
+ page: 1,
+ pageSize: 10,
+ keyword: ''
+ }
+ },
tableSource () {
return this.computeItems.map((item) => {
return {
@@ -78,9 +100,7 @@ export default {
return {
type: 'radio',
selectedRowKeys: this.selectedRowKeys,
- onSelect: (row) => {
- this.$emit('select-compute-item', row.key)
- }
+ onChange: this.onSelectRow
}
}
},
@@ -90,6 +110,22 @@ export default {
this.selectedRowKeys = [newValue]
}
}
+ },
+ methods: {
+ onSelectRow (value) {
+ this.selectedRowKeys = value
+ this.$emit('select-compute-item', value[0])
+ },
+ handleSearch (value) {
+ this.filter = value
+ this.options.keyword = this.filter
+ this.$emit('handle-search-filter', this.options)
+ },
+ handleTableChange (pagination) {
+ this.options.page = pagination.current
+ this.options.pageSize = pagination.pageSize
+ this.$emit('handle-search-filter', this.options)
+ }
}
}
</script>
diff --git a/src/views/compute/wizard/DiskOfferingSelection.vue b/src/views/compute/wizard/DiskOfferingSelection.vue
index f563745..33d94ac 100644
--- a/src/views/compute/wizard/DiskOfferingSelection.vue
+++ b/src/views/compute/wizard/DiskOfferingSelection.vue
@@ -16,20 +16,37 @@
// under the License.
<template>
- <a-table
- :columns="columns"
- :dataSource="tableSource"
- :pagination="{showSizeChanger: true}"
- :rowSelection="rowSelection"
- size="middle"
- >
- <span slot="diskSizeTitle"><a-icon type="hdd" /> {{ $t('disksize') }}</span>
- <span slot="iopsTitle"><a-icon type="rocket" /> {{ $t('minMaxIops') }}</span>
- <template slot="diskSize" slot-scope="text, record">
- <div v-if="record.isCustomized">{{ $t('isCustomized') }}</div>
- <div v-else>{{ record.diskSize }} GB</div>
- </template>
- </a-table>
+ <div>
+ <a-input-search
+ style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+ placeholder="Search"
+ v-model="filter"
+ @search="handleSearch" />
+ <a-table
+ :loading="loading"
+ :columns="columns"
+ :dataSource="tableSource"
+ :pagination="{showSizeChanger: true}"
+ :rowSelection="rowSelection"
+ size="middle"
+ @change="handleTableChange"
+ :scroll="{ y: 225 }"
+ >
+ <span slot="diskSizeTitle"><a-icon type="hdd" /> {{ $t('disksize') }}</span>
+ <span slot="iopsTitle"><a-icon type="rocket" /> {{ $t('minMaxIops') }}</span>
+ <template slot="diskSize" slot-scope="text, record">
+ <div v-if="record.isCustomized">{{ $t('isCustomized') }}</div>
+ <div v-else-if="record.diskSize">{{ record.diskSize }} GB</div>
+ <div v-else>-</div>
+ </template>
+ <template slot="iops" slot-scope="text, record">
+ <span v-if="record.miniops && record.maxiops">{{ record.miniops }} - {{ record.maxiops }}</span>
+ <span v-else-if="record.miniops && !record.maxiops">{{ record.miniops }}</span>
+ <span v-else-if="!record.miniops && record.maxiops">{{ record.maxiops }}</span>
+ <span v-else>-</span>
+ </template>
+ </a-table>
+ </div>
</template>
<script>
@@ -43,18 +60,23 @@ export default {
value: {
type: String,
default: ''
+ },
+ loading: {
+ type: Boolean,
+ default: false
}
},
data () {
return {
+ filter: '',
columns: [
{
dataIndex: 'name',
- title: this.$t('diskOffering'),
+ title: this.$t('diskoffering'),
width: '40%'
},
{
- dataIndex: 'diskSize',
+ dataIndex: 'disksize',
slots: { title: 'diskSizeTitle' },
width: '30%',
scopedSlots: { customRender: 'diskSize' }
@@ -62,20 +84,41 @@ export default {
{
dataIndex: 'iops',
slots: { title: 'iopsTitle' },
- width: '30%'
+ width: '30%',
+ scopedSlots: { customRender: 'iops' }
}
],
- selectedRowKeys: []
+ selectedRowKeys: ['0'],
+ dataItems: []
}
},
+ created () {
+ this.dataItems = []
+ this.dataItems.push({
+ id: '0',
+ name: this.$t('noselect'),
+ diskSize: undefined,
+ miniops: undefined,
+ maxiops: undefined,
+ isCustomized: undefined
+ })
+ },
computed: {
+ options () {
+ return {
+ page: 1,
+ pageSize: 10,
+ keyword: ''
+ }
+ },
tableSource () {
- return this.items.map((item) => {
+ return this.dataItems.map((item) => {
return {
key: item.id,
name: item.name,
diskSize: item.disksize,
- iops: `${item.miniops} – ${item.maxiops}`,
+ miniops: item.miniops,
+ maxiops: item.maxiops,
isCustomized: item.iscustomized
}
})
@@ -84,9 +127,7 @@ export default {
return {
type: 'radio',
selectedRowKeys: this.selectedRowKeys,
- onSelect: (row) => {
- this.$emit('select-disk-offering-item', row.key)
- }
+ onChange: this.onSelectRow
}
}
},
@@ -95,6 +136,27 @@ export default {
if (newValue && newValue !== oldValue) {
this.selectedRowKeys = [newValue]
}
+ },
+ items (newData, oldData) {
+ if (newData && newData.length > 0) {
+ this.dataItems = this.dataItems.concat(newData)
+ }
+ }
+ },
+ methods: {
+ onSelectRow (value) {
+ this.selectedRowKeys = value
+ this.$emit('select-disk-offering-item', value[0])
+ },
+ handleSearch (value) {
+ this.filter = value
+ this.options.keyword = this.filter
+ this.$emit('handle-search-filter', this.options)
+ },
+ handleTableChange (pagination) {
+ this.options.page = pagination.current
+ this.options.pageSize = pagination.pageSize
+ this.$emit('handle-search-filter', this.options)
}
}
}
diff --git a/src/views/compute/wizard/DiskSizeSelection.vue b/src/views/compute/wizard/DiskSizeSelection.vue
index f973741..bec025a 100644
--- a/src/views/compute/wizard/DiskSizeSelection.vue
+++ b/src/views/compute/wizard/DiskSizeSelection.vue
@@ -22,14 +22,13 @@
<a-slider
:min="0"
:max="1024"
- v-decorator="[inputDecorator]"
+ v-model="inputValue"
+ @change="($event) => updateDickSize($event)"
/>
</a-col>
<a-col :span="4">
<a-input-number
- v-decorator="[inputDecorator, {
- rules: [{ required: false, message: 'Please enter a number' }]
- }]"
+ v-model="inputValue"
:formatter="value => `${value} GB`"
:parser="value => value.replace(' GB', '')"
/>
@@ -46,6 +45,16 @@ export default {
type: String,
default: ''
}
+ },
+ data () {
+ return {
+ inputValue: 0
+ }
+ },
+ methods: {
+ updateDickSize (value) {
+ this.$emit('update-disk-size', this.inputDecorator, value)
+ }
}
}
</script>
diff --git a/src/views/compute/wizard/NetworkConfiguration.vue b/src/views/compute/wizard/NetworkConfiguration.vue
index d647e9d..c53ed19 100644
--- a/src/views/compute/wizard/NetworkConfiguration.vue
+++ b/src/views/compute/wizard/NetworkConfiguration.vue
@@ -18,21 +18,24 @@
<template>
<a-table
:columns="columns"
- :dataSource="items"
+ :dataSource="dataItems"
:pagination="{showSizeChanger: true}"
:rowSelection="rowSelection"
:rowKey="record => record.id"
size="middle"
+ :scroll="{ y: 225 }"
>
- <template v-slot:ipAddress="text">
+ <template slot="ipAddress" slot-scope="text, record">
<a-input
- :value="text"
- ></a-input>
+ style="width: 150px;"
+ :placeholder="$t('ipAddress')"
+ @change="($event) => updateNetworkData('ipAddress', record.id, $event.target.value)" />
</template>
- <template v-slot:macAddress="text">
+ <template slot="macAddress" slot-scope="text, record">
<a-input
- :value="text"
- ></a-input>
+ style="width: 150px;"
+ :placeholder="$t('macAddress')"
+ @change="($event) => updateNetworkData('macAddress', record.id, $event.target.value)" />
</template>
</a-table>
</template>
@@ -52,6 +55,7 @@ export default {
},
data () {
return {
+ networks: [],
columns: [
{
dataIndex: 'name',
@@ -71,17 +75,24 @@ export default {
scopedSlots: { customRender: 'macAddress' }
}
],
- selectedRowKeys: []
+ selectedRowKeys: [],
+ dataItems: []
}
},
+ beforeCreate () {
+ this.dataItems = []
+ },
+ created () {
+ this.dataItems = this.items
+ this.selectedRowKeys = [this.dataItems[0].id]
+ this.$emit('select-default-network-item', this.selectedRowKeys)
+ },
computed: {
rowSelection () {
return {
type: 'radio',
selectedRowKeys: this.selectedRowKeys,
- onSelect: (row) => {
- this.$emit('select-default-network-item', row.key)
- }
+ onChange: this.onSelectRow
}
}
},
@@ -90,6 +101,38 @@ export default {
if (newValue && newValue !== oldValue) {
this.selectedRowKeys = [newValue]
}
+ },
+ items (newData, oldData) {
+ if (newData && newData.length > 0) {
+ this.dataItems = newData
+ const keyEx = this.dataItems.filter((item) => this.selectedRowKeys.includes(item.id))
+ if (!keyEx || keyEx.length === 0) {
+ this.selectedRowKeys = [this.dataItems[0].id]
+ }
+ }
+ }
+ },
+ methods: {
+ onSelectRow (value) {
+ this.selectedRowKeys = value
+ this.$emit('select-default-network-item', value[0])
+ },
+ updateNetworkData (name, key, value) {
+ if (this.networks.length === 0) {
+ const networkItem = {}
+ networkItem.key = key
+ networkItem[name] = value
+ this.networks.push(networkItem)
+ this.$emit('update-network-config', this.networks)
+ return
+ }
+
+ this.networks.filter((item, index) => {
+ if (item.key === key) {
+ this.$set(this.networks[index], name, value)
+ }
+ })
+ this.$emit('update-network-config', this.networks)
}
}
}
diff --git a/src/views/compute/wizard/NetworkSelection.vue b/src/views/compute/wizard/NetworkSelection.vue
index 5eeeb34..21151f2 100644
--- a/src/views/compute/wizard/NetworkSelection.vue
+++ b/src/views/compute/wizard/NetworkSelection.vue
@@ -16,29 +16,39 @@
// under the License.
<template>
- <a-table
- :columns="columns"
- :dataSource="networkItems"
- :rowKey="record => record.id"
- :pagination="{showSizeChanger: true}"
- :rowSelection="rowSelection"
- >
- <a-list
- slot="expandedRowRender"
- slot-scope="record"
- :key="record.id"
- :dataSource="getDetails(record)"
- size="small"
+ <div>
+ <a-input-search
+ style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+ placeholder="Search"
+ v-model="filter"
+ @search="handleSearch" />
+ <a-table
+ :loading="loading"
+ :columns="columns"
+ :dataSource="networkItems"
+ :rowKey="record => record.id"
+ :pagination="{showSizeChanger: true, size: 'small'}"
+ :rowSelection="rowSelection"
+ @change="handleTableChange"
+ :scroll="{ y: 225 }"
>
- <a-list-item slot="renderItem" slot-scope="item" :key="item.id">
- <a-list-item-meta
- :description="item.description"
- >
- <template v-slot:title>{{ item.title }}</template>
- </a-list-item-meta>
- </a-list-item>
- </a-list>
- </a-table>
+ <a-list
+ slot="expandedRowRender"
+ slot-scope="record"
+ :key="record.id"
+ :dataSource="getDetails(record)"
+ size="small"
+ >
+ <a-list-item slot="renderItem" slot-scope="item" :key="item.id">
+ <a-list-item-meta
+ :description="item.description"
+ >
+ <template v-slot:title>{{ item.title }}</template>
+ </a-list-item-meta>
+ </a-list-item>
+ </a-list>
+ </a-table>
+ </div>
</template>
<script>
@@ -56,16 +66,28 @@ export default {
value: {
type: Array,
default: () => []
+ },
+ loading: {
+ type: Boolean,
+ default: false
}
},
data () {
return {
+ filter: '',
selectedRowKeys: [],
vpcs: [],
filteredInfo: null
}
},
computed: {
+ options () {
+ return {
+ page: 1,
+ pageSize: 10,
+ keyword: ''
+ }
+ },
columns () {
let vpcFilter = []
if (this.vpcs) {
@@ -146,6 +168,16 @@ export default {
description: network.networkofferingdisplaytext
}
]
+ },
+ handleSearch (value) {
+ this.filter = value
+ this.options.keyword = this.filter
+ this.$emit('handle-search-filter', this.options)
+ },
+ handleTableChange (pagination) {
+ this.options.page = pagination.current
+ this.options.pageSize = pagination.pageSize
+ this.$emit('handle-search-filter', this.options)
}
}
}
diff --git a/src/views/compute/wizard/NetworksCreation.vue b/src/views/compute/wizard/NetworksCreation.vue
deleted file mode 100644
index a708402..0000000
--- a/src/views/compute/wizard/NetworksCreation.vue
+++ /dev/null
@@ -1,150 +0,0 @@
-// 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>
- <a-table
- v-if="networkItems.length > 0"
- :columns="columns"
- :dataSource="networkItems"
- :pagination="false"
- >
- <template v-slot:name="text">
- <a-input
- :value="text"
- ></a-input>
- </template>
- <template v-slot:operation>
- <a-popconfirm
- v-if="networkItems.length"
- title="Sure to delete?"
- @confirm="removeItem()"
- >
- <a-button type="link">Delete</a-button>
- </a-popconfirm>
- </template>
- <template v-slot:networkOffering>
- <a-select
- :placeholder="$t('networkOfferingId')"
- :options="networkOfferingOptions"
- ></a-select>
- </template>
- <template v-slot:vpc>
- <a-select
- :placeholder="$t('vpc')"
- :options="vpcOptions"
- ></a-select>
- </template>
- </a-table>
-
- <div style="text-align: right; margin-top: 1rem;">
- <a-button
- type="primary"
- @click="addNewItem"
- >{{ $t('addAnotherNetwork') }}
- </a-button>
- </div>
- </div>
-</template>
-
-<script>
-import { api } from '@/api'
-import store from '@/store'
-import _ from 'lodash'
-
-/*
- * ToDo: Implement real functionality
- */
-export default {
- name: 'NetworkCreation',
- data () {
- return {
- networkItems: [{}],
- columns: [
- {
- dataIndex: 'name',
- title: this.$t('networks'),
- scopedSlots: { customRender: 'name' },
- width: '30%'
- },
- {
- dataIndex: 'offering',
- title: this.$t('networkOfferingId'),
- scopedSlots: { customRender: 'networkOffering' },
- width: '30%'
- },
- {
- dataIndex: 'vpcName',
- title: this.$t('VPC'),
- scopedSlots: { customRender: 'vpc' },
- width: '30%'
- },
- {
- dataIndex: 'action',
- scopedSlots: { customRender: 'operation' },
- width: '10%'
- }
- ],
- networkOfferings: [],
- vpcs: []
- }
- },
- computed: {
- networkOfferingOptions () {
- return this.networkOfferings.map((offering) => {
- return {
- label: offering.name,
- value: offering.id
- }
- })
- },
- vpcOptions () {
- return this.vpcs.map((vpc) => {
- return {
- label: vpc.name,
- value: vpc.id
- }
- })
- }
- },
- created () {
- api('listNetworkOfferings', {
- // ToDo: Add the zoneId
- }).then((response) => {
- this.networkOfferings = _.get(response, 'listnetworkofferingsresponse.networkoffering')
- })
- // ToDo: Remove this redundant api call – see the NetworkSelection component
- api('listVPCs', {
- projectid: store.getters.project.id
- }).then((response) => {
- this.vpcs = _.get(response, 'listvpcsresponse.vpc')
- })
- },
- methods: {
- addNewItem () {
- this.networkItems.push({})
- },
- removeItem () {
- this.networkItems.pop()
- }
- }
-}
-</script>
-
-<style scoped>
-
-</style>
diff --git a/src/views/compute/wizard/SshKeyPairSelection.vue b/src/views/compute/wizard/SshKeyPairSelection.vue
index 5e62a24..420f406 100644
--- a/src/views/compute/wizard/SshKeyPairSelection.vue
+++ b/src/views/compute/wizard/SshKeyPairSelection.vue
@@ -16,16 +16,26 @@
// under the License.
<template>
- <a-table
- :columns="columns"
- :dataSource="tableSource"
- :pagination="{showSizeChanger: true}"
- :rowSelection="rowSelection"
- size="middle"
- >
- <template v-slot:account><a-icon type="user" /> {{ $t('account') }}</template>
- <template v-slot:domain><a-icon type="block" /> {{ $t('domain') }}</template>
- </a-table>
+ <div>
+ <a-input-search
+ style="width: 25vw;float: right;margin-bottom: 10px; z-index: 8"
+ placeholder="Search"
+ v-model="filter"
+ @search="handleSearch" />
+ <a-table
+ :loading="loading"
+ :columns="columns"
+ :dataSource="tableSource"
+ :pagination="{showSizeChanger: true}"
+ :rowSelection="rowSelection"
+ size="middle"
+ @change="handleTableChange"
+ :scroll="{ y: 225 }"
+ >
+ <template v-slot:account><a-icon type="user" /> {{ $t('account') }}</template>
+ <template v-slot:domain><a-icon type="block" /> {{ $t('domain') }}</template>
+ </a-table>
+ </div>
</template>
<script>
@@ -39,10 +49,15 @@ export default {
value: {
type: String,
default: ''
+ },
+ loading: {
+ type: Boolean,
+ default: false
}
},
data () {
return {
+ filter: '',
columns: [
{
dataIndex: 'name',
@@ -60,12 +75,28 @@ export default {
width: '30%'
}
],
- selectedRowKeys: []
+ selectedRowKeys: [this.$t('noselect')],
+ dataItems: []
}
},
+ created () {
+ this.dataItems = []
+ this.dataItems.push({
+ name: this.$t('noselect'),
+ account: '-',
+ domain: '-'
+ })
+ },
computed: {
+ options () {
+ return {
+ page: 1,
+ pageSize: 10,
+ keyword: ''
+ }
+ },
tableSource () {
- return this.items.map((item) => {
+ return this.dataItems.map((item) => {
return {
key: item.name,
name: item.name,
@@ -78,9 +109,7 @@ export default {
return {
type: 'radio',
selectedRowKeys: this.selectedRowKeys,
- onSelect: (row) => {
- this.$emit('select-ssh-key-pair-item', row.key)
- }
+ onChange: this.onSelectRow
}
}
},
@@ -89,6 +118,27 @@ export default {
if (newValue && newValue !== oldValue) {
this.selectedRowKeys = [newValue]
}
+ },
+ items (newData, oldData) {
+ if (newData && newData.length > 0) {
+ this.dataItems = this.dataItems.concat(newData)
+ }
+ }
+ },
+ methods: {
+ onSelectRow (value) {
+ this.selectedRowKeys = value
+ this.$emit('select-ssh-key-pair-item', value[0])
+ },
+ handleSearch (value) {
+ this.filter = value
+ this.options.keyword = this.filter
+ this.$emit('handle-search-filter', this.options)
+ },
+ handleTableChange (pagination) {
+ this.options.page = pagination.current
+ this.options.pageSize = pagination.pageSize
+ this.$emit('handle-search-filter', this.options)
}
}
}
diff --git a/src/views/compute/wizard/TemplateIsoRadioGroup.vue b/src/views/compute/wizard/TemplateIsoRadioGroup.vue
index cb879e1..594b2d0 100644
--- a/src/views/compute/wizard/TemplateIsoRadioGroup.vue
+++ b/src/views/compute/wizard/TemplateIsoRadioGroup.vue
@@ -17,37 +17,46 @@
<template>
<a-form-item>
- <a-radio-group
- v-for="(os, osIndex) in osList"
- :key="osIndex"
- class="radio-group"
- v-decorator="[inputDecorator, {
- rules: [{ required: true, message: 'Please select option' }]
- }]"
- >
- <a-radio
- class="radio-group__radio"
- :value="os.id"
- >
- {{ os.displaytext }}
- <a-tag
- :visible="os.ispublic && !os.isfeatured"
- color="blue"
- >{{ $t('isPublic') }}</a-tag>
- <a-tag
- :visible="os.isfeatured"
- color="green"
- >{{ $t('isFeatured') }}</a-tag>
- <a-tag
- :visible="isSelf(os)"
- color="orange"
- >{{ $t('isSelf') }}</a-tag>
- <a-tag
- :visible="isShared(os)"
- color="cyan"
- >{{ $t('isShared') }}</a-tag>
- </a-radio>
- </a-radio-group>
+ <a-list
+ class="form-item-scroll"
+ itemLayout="vertical"
+ size="small"
+ :dataSource="osList"
+ :pagination="pagination">
+ <a-list-item slot="renderItem" slot-scope="os, osIndex" key="os.id">
+ <a-radio-group
+ class="radio-group"
+ :key="osIndex"
+ v-model="value"
+ @change="($event) => updateSelectionTemplateIso($event.target.value)">
+ <a-radio
+ class="radio-group__radio"
+ :value="os.id">
+ {{ os.displaytext }}
+ <a-tag
+ :visible="os.ispublic && !os.isfeatured"
+ color="blue"
+ @click="onFilterTag('is: public')"
+ >{{ $t('isPublic') }}</a-tag>
+ <a-tag
+ :visible="os.isfeatured"
+ color="green"
+ @click="onFilterTag('is: featured')"
+ >{{ $t('isFeatured') }}</a-tag>
+ <a-tag
+ :visible="isSelf(os)"
+ color="orange"
+ @click="onFilterTag('is: self')"
+ >{{ $t('isSelf') }}</a-tag>
+ <a-tag
+ :visible="isShared(os)"
+ color="cyan"
+ @click="onFilterTag('is: shared')"
+ >{{ $t('isShared') }}</a-tag>
+ </a-radio>
+ </a-radio-group>
+ </a-list-item>
+ </a-list>
</a-form-item>
</template>
@@ -64,6 +73,45 @@ export default {
inputDecorator: {
type: String,
default: ''
+ },
+ selected: {
+ type: String,
+ default: ''
+ },
+ itemCount: {
+ type: Number,
+ default: 0
+ }
+ },
+ data () {
+ return {
+ value: '',
+ page: 1,
+ pageSize: 10
+ }
+ },
+ created () {
+ this.value = this.selected
+ this.$emit('emit-update-template-iso', this.inputDecorator, this.value)
+ },
+ watch: {
+ inputDecorator (value) {
+ if (value === 'templateid') {
+ this.value = this.selected
+ }
+ }
+ },
+ computed: {
+ pagination () {
+ return {
+ size: 'small',
+ page: 1,
+ pageSize: 10,
+ total: this.itemCount,
+ showSizeChanger: true,
+ onChange: this.onChangePage,
+ onShowSizeChange: this.onChangePageSize
+ }
}
},
methods: {
@@ -72,6 +120,22 @@ export default {
},
isSelf (item) {
return !item.ispublic && (item.account === store.getters.userInfo.account)
+ },
+ updateSelectionTemplateIso (id) {
+ this.$emit('emit-update-template-iso', this.inputDecorator, id)
+ },
+ onChangePage (page, pageSize) {
+ this.pagination.page = page
+ this.pagination.pageSize = pageSize
+ this.$forceUpdate()
+ },
+ onChangePageSize (page, pageSize) {
+ this.pagination.page = page
+ this.pagination.pageSize = pageSize
+ this.$forceUpdate()
+ },
+ onFilterTag (tag) {
+ this.$emit('handle-filter-tag', tag)
}
}
}
@@ -89,4 +153,14 @@ export default {
.ant-tag {
margin-left: 0.4rem;
}
+
+ /deep/.ant-spin-container {
+ max-height: 200px;
+ overflow-y: auto;
+ }
+
+ .pagination {
+ margin-top: 20px;
+ float: right;
+ }
</style>
diff --git a/src/views/compute/wizard/TemplateIsoSelection.vue b/src/views/compute/wizard/TemplateIsoSelection.vue
index 27796fe..b26f893 100644
--- a/src/views/compute/wizard/TemplateIsoSelection.vue
+++ b/src/views/compute/wizard/TemplateIsoSelection.vue
@@ -16,27 +16,33 @@
// under the License.
<template>
- <a-tabs :defaultActiveKey="Object.keys(osTypes)[0]" v-if="view === TAB_VIEW">
- <a-button icon="search" slot="tabBarExtraContent" @click="() => toggleView(FILTER_VIEW)"/>
- <a-tab-pane v-for="(osList, osName) in osTypes" :key="osName">
- <span slot="tab">
- <os-logo :os-name="osName"></os-logo>
- </span>
- <TemplateIsoRadioGroup
- :osList="osList"
- :input-decorator="inputDecorator"
- ></TemplateIsoRadioGroup>
- </a-tab-pane>
- </a-tabs>
- <div v-else>
- <a-input class="search-input" v-model="filter">
- <a-icon slot="prefix" type="search"/>
- <a-icon slot="addonAfter" type="close" @click="toggleView(TAB_VIEW)"/>
- </a-input>
- <TemplateIsoRadioGroup
- :osList="filteredItems"
- :input-decorator="inputDecorator"
- ></TemplateIsoRadioGroup>
+ <div>
+ <a-input-search
+ class="search-input"
+ placeholder="Search"
+ v-model="filter"
+ @search="filterDataSource"/>
+ <a-spin :spinning="loading">
+ <a-tabs
+ tabPosition="top"
+ :animated="false"
+ :defaultActiveKey="Object.keys(dataSource)[0]">
+ <a-tab-pane v-for="(osList, osName) in dataSource" :key="osName">
+ <span slot="tab">
+ <os-logo :os-name="osName"></os-logo>
+ </span>
+ <TemplateIsoRadioGroup
+ :osType="osName"
+ :osList="dataSource[osName]"
+ :input-decorator="inputDecorator"
+ :selected="checkedValue"
+ :itemCount="itemCount[osName]"
+ @handle-filter-tag="filterDataSource"
+ @emit-update-template-iso="updateTemplateIso"
+ ></TemplateIsoRadioGroup>
+ </a-tab-pane>
+ </a-tabs>
+ </a-spin>
</div>
</template>
@@ -45,9 +51,7 @@ import OsLogo from '@/components/widgets/OsLogo'
import { getNormalizedOsName } from '@/utils/icons'
import _ from 'lodash'
import TemplateIsoRadioGroup from '@views/compute/wizard/TemplateIsoRadioGroup'
-
-export const TAB_VIEW = 1
-export const FILTER_VIEW = 2
+import store from '@/store'
export default {
name: 'TemplateIsoSelection',
@@ -60,27 +64,53 @@ export default {
inputDecorator: {
type: String,
default: ''
+ },
+ selected: {
+ type: String,
+ default: ''
+ },
+ loading: {
+ type: Boolean,
+ default: false
}
},
data () {
return {
- TAB_VIEW: TAB_VIEW,
- FILTER_VIEW: FILTER_VIEW,
- visible: false,
filter: '',
filteredItems: this.items,
- view: TAB_VIEW
+ checkedValue: '',
+ dataSource: {},
+ itemCount: {}
+ }
+ },
+ watch: {
+ items (items) {
+ this.filteredItems = []
+ this.checkedValue = ''
+ if (items && items.length > 0) {
+ this.filteredItems = items
+ this.checkedValue = items[0].id
+ }
+ this.dataSource = this.mappingDataSource()
+ },
+ inputDecorator (newValue, oldValue) {
+ if (newValue !== oldValue) {
+ this.filter = ''
+ }
}
},
- computed: {
- osTypes () {
+ methods: {
+ mappingDataSource () {
let mappedItems = {}
- this.items.forEach((os) => {
+ const itemCount = {}
+ this.filteredItems.forEach((os) => {
const osName = getNormalizedOsName(os.ostypename)
if (Array.isArray(mappedItems[osName])) {
mappedItems[osName].push(os)
+ itemCount[osName] = itemCount[osName] + 1
} else {
mappedItems[osName] = [os]
+ itemCount[osName] = 1
}
})
mappedItems = _.mapValues(mappedItems, (list) => {
@@ -90,24 +120,50 @@ export default {
nonFeaturedItems = _.sortBy(nonFeaturedItems, (item) => item.displaytext.toLowerCase())
return featuredItems.concat(nonFeaturedItems) // pin featured isos/templates at the top
})
+ this.itemCount = itemCount
return mappedItems
- }
- },
- watch: {
- items (items) {
- this.filteredItems = items
},
- filter (filterString) {
- if (filterString !== '') {
- this.filteredItems = this.filteredItems.filter((item) => item.displaytext.toLowerCase().includes(filterString))
+ updateTemplateIso (name, id) {
+ this.$emit('update-template-iso', name, id)
+ },
+ filterDataSource (strQuery) {
+ if (strQuery !== '' && strQuery.includes('is:')) {
+ this.filteredItems = []
+ this.filter = strQuery
+ const filters = strQuery.split(';')
+ filters.forEach((filter) => {
+ const query = filter.replace(/ /g, '')
+ const data = this.filterDataSourceByTag(query)
+ this.filteredItems = this.filteredItems.concat(data)
+ })
+ } else if (strQuery !== '') {
+ this.filteredItems = this.items.filter((item) => item.displaytext.toLowerCase().includes(strQuery.toLowerCase()))
} else {
this.filteredItems = this.items
}
- }
- },
- methods: {
- toggleView (view) {
- this.view = view
+ this.dataSource = this.mappingDataSource()
+ },
+ filterDataSourceByTag (tag) {
+ let arrResult = []
+ if (tag.includes('public')) {
+ arrResult = this.items.filter((item) => {
+ return item.ispublic && item.isfeatured
+ })
+ } else if (tag.includes('featured')) {
+ arrResult = this.items.filter((item) => {
+ return item.isfeatured
+ })
+ } else if (tag.includes('self')) {
+ arrResult = this.items.filter((item) => {
+ return !item.ispublic && (item.account === store.getters.userInfo.account)
+ })
+ } else if (tag.includes('shared')) {
+ arrResult = this.items.filter((item) => {
+ return !item.ispublic && (item.account !== store.getters.userInfo.account)
+ })
+ }
+
+ return arrResult
}
}
}
@@ -115,6 +171,21 @@ export default {
<style lang="less" scoped>
.search-input {
- margin: 0.5rem 0 1rem;
+ width: 25vw;
+ z-index: 8;
+ position: absolute;
+ top: 11px;
+ right: 10px;
+
+ @media (max-width: 600px) {
+ position: relative;
+ width: 100%;
+ top: 0;
+ right: 0;
+ }
+ }
+
+ /deep/.ant-tabs-nav-scroll {
+ min-height: 45px;
}
</style>
diff --git a/src/views/iam/SSLCertificateTab.vue b/src/views/iam/SSLCertificateTab.vue
index c5b2a79..a6941f0 100644
--- a/src/views/iam/SSLCertificateTab.vue
+++ b/src/views/iam/SSLCertificateTab.vue
@@ -81,7 +81,7 @@ export default {
detailColumn: [],
detail: [],
page: 1,
- pageSize: 20,
+ pageSize: 10,
quickview: false,
loading: false
}
diff --git a/src/views/image/IsoZones.vue b/src/views/image/IsoZones.vue
index 5cb9582..b08bf99 100644
--- a/src/views/image/IsoZones.vue
+++ b/src/views/image/IsoZones.vue
@@ -70,7 +70,7 @@ export default {
detailColumn: [],
detail: [],
page: 1,
- pageSize: 20,
+ pageSize: 10,
itemCount: 0,
fetchLoading: false
}
diff --git a/src/views/image/TemplateZones.vue b/src/views/image/TemplateZones.vue
index 5a86128..46661bb 100644
--- a/src/views/image/TemplateZones.vue
+++ b/src/views/image/TemplateZones.vue
@@ -68,7 +68,7 @@ export default {
columns: [],
dataSource: [],
page: 1,
- pageSize: 20,
+ pageSize: 10,
itemCount: 0,
fetchLoading: false
}
diff --git a/src/views/network/VpcTab.vue b/src/views/network/VpcTab.vue
index 2b4d325..d0d8dbb 100644
--- a/src/views/network/VpcTab.vue
+++ b/src/views/network/VpcTab.vue
@@ -19,7 +19,7 @@
<a-spin :spinning="fetchLoading">
<a-tabs
:activeKey="currentTab"
- :tabPosition="device === 'tablet' || device === 'mobile' ? 'top' : 'left'"
+ :tabPosition="device === 'mobile' ? 'top' : 'left'"
:animated="false"
@change="handleChangeTab">
<a-tab-pane :tab="$t('details')" key="details">