diff --git a/src/components/header/ProjectMenu.vue b/src/components/header/ProjectMenu.vue index 2c922d18a..f3ea38bb9 100644 --- a/src/components/header/ProjectMenu.vue +++ b/src/components/header/ProjectMenu.vue @@ -67,9 +67,9 @@ export default { if (json && json.listprojectsresponse && json.listprojectsresponse.project) { this.projects.push(...json.listprojectsresponse.project) } - const currentProject = Vue.ls.get(CURRENT_PROJECT) + const currentProject = Vue.ls.get(CURRENT_PROJECT) || {} for (var project of this.projects) { - if (project.id === currentProject.id) { + if (project && currentProject && project.id === currentProject.id) { this.setSelectedProject(project) break } diff --git a/src/locales/en.json b/src/locales/en.json index fad214622..285f1823c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1151,6 +1151,8 @@ "message.required.traffic.type": "Error in configuration! All required traffic types should be added and with multiple physical networks each network should have a label.", "message.desc.primary.storage": "Each cluster must contain one or more primary storage servers, and we will add the first one now. Primary storage contains the disk volumes for all the VMs running on hosts in the cluster. Use any standards-compliant protocol that is supported by the underlying hypervisor.", "message.desc.secondary.storage": "Each zone must have at least one NFS or secondary storage server, and we will add the first one now. Secondary storage stores VM templates, ISO images, and VM disk volume snapshots. This server must be available to all hosts in the zone.

Provide the IP address and exported path.", +"message.error.required.input": "Please enter input", +"message.error.invalid.range": "Please enter values from {min} to {max}", "label.name": "Name", "label.ipv4.dns1": "IPv4 DNS1", "label.ipv4.dns2": "IPv4 DNS2", @@ -1266,5 +1268,10 @@ "label.launch.zone": "Launch Zone", "label.done": "Done", "label.fix.errors": "Fix errors", -"error.something.went.wrong.please.correct.the.following": "Something went wrong; please correct the following" +"error.something.went.wrong.please.correct.the.following": "Something went wrong; please correct the following", +"filter": "Filter", +"featured": "Featured", +"community": "Community", +"selfexecutable": "Self", +"sharedexecutable": "Shared" } diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue index 47b10af04..07f471fcc 100644 --- a/src/views/AutogenView.vue +++ b/src/views/AutogenView.vue @@ -396,6 +396,8 @@ export default { if (this.searchQuery !== '') { if (this.apiName === 'listRoles') { params.name = this.searchQuery + } else if (this.apiName === 'quotaEmailTemplateList') { + params.templatetype = this.searchQuery } else { params.keyword = this.searchQuery } diff --git a/src/views/compute/DeployVM.vue b/src/views/compute/DeployVM.vue index f1a4c9eb7..d7b7eee90 100644 --- a/src/views/compute/DeployVM.vue +++ b/src/views/compute/DeployVM.vue @@ -44,21 +44,27 @@ :loading="loading.zones" > - + - + - +

@@ -113,8 +121,22 @@ :items="options.isos" :selected="tabKey" :loading="loading.isos" + :preFillContent="dataPreFill" @update-template-iso="updateFieldValue" > + + + +

@@ -134,13 +156,42 @@ :status="zoneSelected ? 'process' : 'wait'"> @@ -153,12 +204,14 @@ :items="options.diskOfferings" :value="diskOffering ? diskOffering.id : ''" :loading="loading.diskOfferings" + :preFillContent="dataPreFill" @select-disk-offering-item="($event) => updateDiskOffering($event)" @handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)" > @@ -175,6 +228,7 @@ :items="options.affinityGroups" :value="affinityGroupIds" :loading="loading.affinityGroups" + :preFillContent="dataPreFill" @select-affinity-group-item="($event) => updateAffinityGroups($event)" @handle-search-filter="($event) => handleSearchFilter('affinityGroups', $event)" > @@ -191,12 +245,14 @@ :value="networkOfferingIds" :loading="loading.networks" :zoneId="zoneId" + :preFillContent="dataPreFill" @select-network-item="($event) => updateNetworks($event)" @handle-search-filter="($event) => handleSearchFilter('networks', $event)" > @@ -212,6 +268,7 @@ :items="options.sshKeyPairs" :value="sshKeyPair ? sshKeyPair.name : ''" :loading="loading.sshKeyPairs" + :preFillContent="dataPreFill" @select-ssh-key-pair-item="($event) => updateSshKeyPairs($event)" @handle-search-filter="($event) => handleSearchFilter('sshKeyPairs', $event)" /> @@ -261,6 +318,7 @@ import { mixin, mixinDevice } from '@/utils/mixin.js' import store from '@/store' import InfoCard from '@/components/view/InfoCard' +import ComputeOfferingSelection from './wizard/ComputeOfferingSelection' import ComputeSelection from './wizard/ComputeSelection' import DiskOfferingSelection from '@views/compute/wizard/DiskOfferingSelection' import DiskSizeSelection from '@views/compute/wizard/DiskSizeSelection' @@ -281,11 +339,16 @@ export default { DiskSizeSelection, DiskOfferingSelection, InfoCard, + ComputeOfferingSelection, ComputeSelection }, props: { visible: { type: Boolean + }, + preFillContent: { + type: Object, + default: () => {} } }, mixins: [mixin, mixinDevice], @@ -297,6 +360,7 @@ export default { options: { templates: [], isos: [], + hypervisors: [], serviceOfferings: [], diskOfferings: [], zones: [], @@ -313,6 +377,7 @@ export default { deploy: false, templates: false, isos: false, + hypervisors: false, serviceOfferings: false, diskOfferings: false, affinityGroups: false, @@ -324,9 +389,10 @@ export default { hosts: false, groups: false }, - instanceConfig: [], + instanceConfig: {}, template: {}, iso: {}, + hypervisor: '', serviceOffering: {}, diskOffering: {}, affinityGroups: [], @@ -368,10 +434,14 @@ export default { tab: this.$t('ISOs') } ], - tabKey: 'templateid' + tabKey: 'templateid', + dataPreFill: {} } }, computed: { + isNormalAndDomainUser () { + return ['DomainAdmin', 'User'].includes(this.$store.getters.userInfo.roletype) + }, diskSize () { const rootDiskSize = _.get(this.instanceConfig, 'rootdisksize', 0) const customDiskSize = _.get(this.instanceConfig, 'size', 0) @@ -411,23 +481,33 @@ export default { } }, zones: { - list: 'listZones' + list: 'listZones', + isLoad: true, + field: 'zoneid' + }, + hypervisors: { + list: 'listHypervisors', + options: { + zoneid: _.get(this.zone, 'id') + }, + field: 'hypervisor' }, affinityGroups: { list: 'listAffinityGroups', options: { page: 1, pageSize: 10, - keyword: undefined + keyword: undefined, + listall: false } }, sshKeyPairs: { list: 'listSSHKeyPairs', options: { - zoneid: _.get(this.zone, 'id'), page: 1, pageSize: 10, - keyword: undefined + keyword: undefined, + listall: false } }, networks: { @@ -436,6 +516,8 @@ export default { zoneid: _.get(this.zone, 'id'), canusefordeploy: true, projectid: store.getters.project.id, + domainid: store.getters.project.id ? null : store.getters.userInfo.domainid, + account: store.getters.project.id ? null : store.getters.userInfo.account, page: 1, pageSize: 10, keyword: undefined @@ -443,26 +525,37 @@ export default { }, pods: { list: 'listPods', + isLoad: !this.isNormalAndDomainUser, options: { zoneid: _.get(this.zone, 'id') - } + }, + field: 'podid' }, clusters: { list: 'listClusters', + isLoad: !this.isNormalAndDomainUser, options: { zoneid: _.get(this.zone, 'id') - } + }, + field: 'clusterid' }, hosts: { list: 'listHosts', + isLoad: !this.isNormalAndDomainUser, options: { zoneid: _.get(this.zone, 'id'), state: 'Up', type: 'Routing' - } + }, + field: 'hostid' }, groups: { - list: 'listInstanceGroups' + list: 'listInstanceGroups', + options: { + listall: false + }, + isLoad: true, + field: 'group' } } }, @@ -477,6 +570,14 @@ export default { } }) }, + hypervisorSelectOptions () { + return this.options.hypervisors.map((hypervisor) => { + return { + label: hypervisor.name, + value: hypervisor.name + } + }) + }, podSelectOptions () { return this.options.pods.map((pod) => { return { @@ -527,6 +628,8 @@ export default { instanceConfig (instanceConfig) { this.template = _.find(this.options.templates, (option) => option.id === instanceConfig.templateid) this.iso = _.find(this.options.isos, (option) => option.id === instanceConfig.isoid) + var hypervisorItem = _.find(this.options.hypervisors, (option) => option.name === instanceConfig.hypervisor) + this.hypervisor = hypervisorItem ? hypervisorItem.name : null this.serviceOffering = _.find(this.options.serviceOfferings, (option) => option.id === instanceConfig.computeofferingid) this.diskOffering = _.find(this.options.diskOfferings, (option) => option.id === instanceConfig.diskofferingid) this.zone = _.find(this.options.zones, (option) => option.id === instanceConfig.zoneid) @@ -551,6 +654,9 @@ export default { this.vm.templatename = this.iso.displaytext this.vm.ostypeid = this.iso.ostypeid this.vm.ostypename = this.iso.ostypename + if (this.hypervisor) { + this.vm.hypervisor = this.hypervisor + } } if (this.serviceOffering) { @@ -572,7 +678,7 @@ export default { } } }, - beforeCreate () { + created () { this.form = this.$form.createForm(this, { onValuesChange: (props, fields) => { if (fields.isoid) { @@ -584,36 +690,63 @@ export default { this.form.setFieldsValue({ isoid: null }) } this.instanceConfig = { ...this.form.getFieldsValue(), ...fields } - this.vm = this.instanceConfig + this.vm = Object.assign({}, this.instanceConfig) } }) 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: undefined, preserve: true }) this.form.getFieldDecorator('networkids', { 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 - }) + this.form.getFieldDecorator('cpunumber', { initialValue: undefined, preserve: true }) + this.form.getFieldDecorator('cpuSpeed', { initialValue: undefined, preserve: true }) + this.form.getFieldDecorator('memory', { initialValue: undefined, preserve: true }) }, - created () { + mounted () { + this.dataPreFill = this.preFillContent && Object.keys(this.preFillContent).length > 0 ? this.preFillContent : {} this.fetchData() }, + provide () { + return { + vmFetchTemplates: this.fetchAllTemplates, + vmFetchIsos: this.fetchAllIsos, + vmFetchNetworks: this.fetchNetwork + } + }, methods: { + fillValue (field) { + this.form.getFieldDecorator([field], { initialValue: this.dataPreFill[field] }) + }, 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') + if (this.dataPreFill.zoneid) { + this.fetchDataByZone(this.dataPreFill.zoneid) + } else { + _.each(this.params, (param, name) => { + if (param.isLoad) { + this.fetchOptions(param, name) + } + }) + } + this.fetchKeyboard() Vue.nextTick().then(() => { + ['name', 'keyboard', 'userdata'].forEach(this.fillValue) this.instanceConfig = this.form.getFieldsValue() // ToDo: maybe initialize with some other defaults }) }, + async fetchDataByZone (zoneId) { + this.fillValue('zoneid') + this.options.zones = await this.fetchZones() + this.zoneId = zoneId + this.zoneSelected = true + this.tabKey = 'templateid' + await _.each(this.params, (param, name) => { + if (!('isLoad' in param) || param.isLoad) { + this.fetchOptions(param, name, ['zones']) + } + }) + await this.fetchAllTemplates() + }, fetchKeyboard () { const keyboardType = [] keyboardType.push({ @@ -643,6 +776,10 @@ export default { this.$set(this.options, 'keyboards', keyboardType) }, + fetchNetwork () { + const param = this.params.networks + this.fetchOptions(param, 'networks') + }, resetData () { this.vm = {} this.zoneSelected = false @@ -654,13 +791,13 @@ export default { this.tabKey = 'templateid' this.form.setFieldsValue({ templateid: value, - isoid: undefined + isoid: null }) } else if (name === 'isoid') { this.tabKey = 'isoid' this.form.setFieldsValue({ isoid: value, - templateid: undefined + templateid: null }) } else { this.form.setFieldsValue({ @@ -729,8 +866,8 @@ export default { 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))) + if (values.userdata && values.userdata.length > 0) { + deployVmData.userdata = encodeURIComponent(btoa(this.sanitizeReverse(values.userdata))) } // step 2: select template/iso if (this.tabKey === 'templateid') { @@ -741,15 +878,29 @@ export default { if (values.rootdisksize && values.rootdisksize > 0) { deployVmData.rootdisksize = values.rootdisksize } + if (values.hypervisor && values.hypervisor.length > 0) { + deployVmData.hypervisor = values.hypervisor + } // step 3: select service offering deployVmData.serviceofferingid = values.computeofferingid + if (values.cpunumber || values.cpuspeed || values.memory) { + if (values.cpunumber) { + deployVmData['details[0].cpuNumber'] = values.cpunumber + } + if (values.cpuspeed) { + deployVmData['details[0].cpuSpeed'] = values.cpuspeed + } + if (values.memory) { + deployVmData['details[0].memory'] = values.memory + } + } // 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(',') + 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++) { @@ -804,6 +955,20 @@ export default { }) }) }, + fetchZones () { + return new Promise((resolve) => { + this.loading.zones = true + const param = this.params.zones + api(param.list, { listall: true }).then(json => { + const zones = json.listzonesresponse.zone || [] + resolve(zones) + }).catch(function (error) { + console.log(error.stack) + }).finally(() => { + this.loading.zones = false + }) + }) + }, fetchOptions (param, name, exclude) { if (exclude && exclude.length > 0) { if (exclude.includes(name)) { @@ -814,7 +979,9 @@ export default { param.loading = true param.opts = [] const options = param.options || {} - options.listall = true + if (!('listall' in options)) { + options.listall = true + } api(param.list, options).then((response) => { param.loading = false _.map(response, (responseItem, responseKey) => { @@ -833,6 +1000,9 @@ export default { param.opts = response this.options[name] = response this.$forceUpdate() + if (param.field) { + this.fillValue(param.field) + } }) }) }).catch(function (error) { @@ -869,11 +1039,14 @@ export default { }) }) }, - fetchAllTemplates () { + fetchAllTemplates (filterKey) { const promises = [] this.options.templates = [] this.loading.templates = true this.templateFilter.forEach((filter) => { + if (filterKey && filterKey !== filter) { + return true + } promises.push(this.fetchTemplates(filter)) }) Promise.all(promises).then(response => { @@ -888,11 +1061,14 @@ export default { this.loading.templates = false }) }, - fetchAllIsos () { + fetchAllIsos (filterKey) { const promises = [] this.options.isos = [] this.loading.isos = true this.isoFilter.forEach((filter) => { + if (filterKey && filterKey !== filter) { + return true + } promises.push(this.fetchIsos(filter)) }) Promise.all(promises).then(response => { @@ -908,7 +1084,9 @@ export default { }) }, onSelectZoneId (value) { + this.dataPreFill = {} this.zoneId = value + this.zone = _.find(this.options.zones, (option) => option.id === value) this.zoneSelected = true this.form.setFieldsValue({ clusterid: undefined, @@ -919,7 +1097,9 @@ export default { }) this.tabKey = 'templateid' _.each(this.params, (param, name) => { - this.fetchOptions(param, name, ['zones', 'groups']) + if (!('isLoad' in param) || param.isLoad) { + this.fetchOptions(param, name, ['zones', 'groups']) + } }) this.fetchAllTemplates() }, diff --git a/src/views/compute/wizard/AffinityGroupSelection.vue b/src/views/compute/wizard/AffinityGroupSelection.vue index be14d6f2f..9c2535500 100644 --- a/src/views/compute/wizard/AffinityGroupSelection.vue +++ b/src/views/compute/wizard/AffinityGroupSelection.vue @@ -53,6 +53,10 @@ export default { loading: { type: Boolean, default: false + }, + preFillContent: { + type: Object, + default: () => {} } }, data () { @@ -96,6 +100,17 @@ export default { if (newValue && !_.isEqual(newValue, oldValue)) { this.selectedRowKeys = newValue } + }, + loading () { + if (!this.loading) { + if (this.preFillContent.affinitygroupids) { + this.selectedRowKeys = this.preFillContent.affinitygroupids + this.$emit('select-affinity-group-item', this.preFillContent.affinitygroupids) + } else { + this.selectedRowKeys = [] + this.$emit('select-affinity-group-item', null) + } + } } }, methods: { diff --git a/src/views/compute/wizard/ComputeOfferingSelection.vue b/src/views/compute/wizard/ComputeOfferingSelection.vue new file mode 100644 index 000000000..4403ee494 --- /dev/null +++ b/src/views/compute/wizard/ComputeOfferingSelection.vue @@ -0,0 +1,169 @@ +// 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. + + + + + + diff --git a/src/views/compute/wizard/ComputeSelection.vue b/src/views/compute/wizard/ComputeSelection.vue index e44d17ad9..68ae44219 100644 --- a/src/views/compute/wizard/ComputeSelection.vue +++ b/src/views/compute/wizard/ComputeSelection.vue @@ -16,122 +16,228 @@ // under the License. - - diff --git a/src/views/compute/wizard/DiskOfferingSelection.vue b/src/views/compute/wizard/DiskOfferingSelection.vue index 33d94ac9e..78b06efac 100644 --- a/src/views/compute/wizard/DiskOfferingSelection.vue +++ b/src/views/compute/wizard/DiskOfferingSelection.vue @@ -64,6 +64,10 @@ export default { loading: { type: Boolean, default: false + }, + preFillContent: { + type: Object, + default: () => {} } }, data () { @@ -93,15 +97,7 @@ export default { } }, created () { - this.dataItems = [] - this.dataItems.push({ - id: '0', - name: this.$t('noselect'), - diskSize: undefined, - miniops: undefined, - maxiops: undefined, - isCustomized: undefined - }) + this.initDataItem() }, computed: { options () { @@ -139,11 +135,34 @@ export default { }, items (newData, oldData) { if (newData && newData.length > 0) { + this.initDataItem() this.dataItems = this.dataItems.concat(newData) } + }, + loading () { + if (!this.loading) { + if (this.preFillContent.diskofferingid) { + this.selectedRowKeys = [this.preFillContent.diskofferingid] + this.$emit('select-disk-offering-item', this.preFillContent.diskofferingid) + } else { + this.selectedRowKeys = ['0'] + this.$emit('select-disk-offering-item', '0') + } + } } }, methods: { + initDataItem () { + this.dataItems = [] + this.dataItems.push({ + id: '0', + name: this.$t('noselect'), + diskSize: undefined, + miniops: undefined, + maxiops: undefined, + isCustomized: undefined + }) + }, onSelectRow (value) { this.selectedRowKeys = value this.$emit('select-disk-offering-item', value[0]) diff --git a/src/views/compute/wizard/DiskSizeSelection.vue b/src/views/compute/wizard/DiskSizeSelection.vue index bec025a47..684cb18d4 100644 --- a/src/views/compute/wizard/DiskSizeSelection.vue +++ b/src/views/compute/wizard/DiskSizeSelection.vue @@ -16,22 +16,26 @@ // under the License.