diff --git a/src/locales/en.json b/src/locales/en.json index 651121120..d4ddbffbe 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 731270d83..ce6deab7b 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 8887a5eea..871c380fa 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 eb0a5f723..f1a4c9eb7 100644 --- a/src/views/compute/DeployVM.vue +++ b/src/views/compute/DeployVM.vue @@ -25,131 +25,216 @@ @submit="handleSubmit" layout="vertical" > - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - +
@@ -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; + } diff --git a/src/views/compute/KubernetesServiceTab.vue b/src/views/compute/KubernetesServiceTab.vue index d71d5a666..daa7851cc 100644 --- a/src/views/compute/KubernetesServiceTab.vue +++ b/src/views/compute/KubernetesServiceTab.vue @@ -19,7 +19,7 @@ diff --git a/src/views/compute/wizard/AffinityGroupSelection.vue b/src/views/compute/wizard/AffinityGroupSelection.vue index c13cbcc9a..be14d6f2f 100644 --- a/src/views/compute/wizard/AffinityGroupSelection.vue +++ b/src/views/compute/wizard/AffinityGroupSelection.vue @@ -16,15 +16,24 @@ // under the License. diff --git a/src/views/compute/wizard/ComputeSelection.vue b/src/views/compute/wizard/ComputeSelection.vue index dfe4e4950..e44d17ad9 100644 --- a/src/views/compute/wizard/ComputeSelection.vue +++ b/src/views/compute/wizard/ComputeSelection.vue @@ -16,16 +16,26 @@ // under the License. diff --git a/src/views/compute/wizard/DiskOfferingSelection.vue b/src/views/compute/wizard/DiskOfferingSelection.vue index f56374572..33d94ac9e 100644 --- a/src/views/compute/wizard/DiskOfferingSelection.vue +++ b/src/views/compute/wizard/DiskOfferingSelection.vue @@ -16,20 +16,37 @@ // under the License. diff --git a/src/views/compute/wizard/NetworkConfiguration.vue b/src/views/compute/wizard/NetworkConfiguration.vue index d647e9d70..c53ed19f8 100644 --- a/src/views/compute/wizard/NetworkConfiguration.vue +++ b/src/views/compute/wizard/NetworkConfiguration.vue @@ -18,21 +18,24 @@