This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 3fe2b46dd0b [UI] Add option to specify account/project while deploying
VMs and creating networks (#8919)
3fe2b46dd0b is described below
commit 3fe2b46dd0b00a2dbca8c2bb154e61fe436869cb
Author: Bryan Lima <[email protected]>
AuthorDate: Wed Jun 12 04:10:32 2024 -0300
[UI] Add option to specify account/project while deploying VMs and creating
networks (#8919)
---
ui/public/locales/en.json | 1 +
ui/public/locales/pt_BR.json | 1 +
ui/src/views/compute/AssignInstance.vue | 167 +------------
ui/src/views/compute/DeployVM.vue | 94 +++++++-
ui/src/views/compute/wizard/OwnershipSelection.vue | 257 +++++++++++++++++++++
ui/src/views/network/CreateIsolatedNetworkForm.vue | 171 +++-----------
ui/src/views/network/CreateL2NetworkForm.vue | 144 +++---------
7 files changed, 421 insertions(+), 414 deletions(-)
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index cb279c7a149..86a74384cb7 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -1557,6 +1557,7 @@
"label.ovmnetworklabel": "OVM traffic label",
"label.ovs": "OVS",
"label.owner.account": "Owner Account",
+"label.owner.type": "Owner type",
"label.owners": "Owners",
"label.pa": "Palo Alto",
"label.page": "page",
diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json
index cc8e22d5e47..9d64cc27150 100644
--- a/ui/public/locales/pt_BR.json
+++ b/ui/public/locales/pt_BR.json
@@ -1155,6 +1155,7 @@
"label.ovmnetworklabel": "R\u00f3tulo de tr\u00e1fego OVM",
"label.ovs": "OVS",
"label.owner.account": "Dono da conta",
+"label.owner.type": "Tipo de dono",
"label.owners": "Donos",
"label.pa": "Palo Alto",
"label.page": "p\u00e1gina",
diff --git a/ui/src/views/compute/AssignInstance.vue
b/ui/src/views/compute/AssignInstance.vue
index 9726dc21577..873d10f5770 100644
--- a/ui/src/views/compute/AssignInstance.vue
+++ b/ui/src/views/compute/AssignInstance.vue
@@ -29,86 +29,7 @@
</template>
</a-alert>
- <div class="form__item">
- <p class="form__label">{{ $t('label.accounttype') }}</p>
- <a-select
- v-model:value="selectedAccountType"
- v-focus="true"
- showSearch
- optionFilterProp="value"
- :filterOption="(input, option) => {
- return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
- }">
- <a-select-option :value="$t('label.account')">{{ $t('label.account')
}}</a-select-option>
- <a-select-option :value="$t('label.project')">{{ $t('label.project')
}}</a-select-option>
- </a-select>
- </div>
-
- <div class="form__item">
- <p class="form__label"><span class="required">*</span>{{
$t('label.domain') }}</p>
- <a-select
- @change="changeDomain"
- v-model:value="selectedDomain"
- showSearch
- optionFilterProp="label"
- :filterOption="(input, option) => {
- return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
- }" >
- <a-select-option v-for="domain in domains" :key="domain.name"
:value="domain.id" :label="domain.path || domain.name || domain.description">
- <span>
- <resource-icon v-if="domain && domain.icon"
:image="domain.icon.base64image" size="1x" style="margin-right: 5px"/>
- <block-outlined v-else style="margin-right: 5px" />
- {{ domain.path || domain.name || domain.description }}
- </span>
- </a-select-option>
- </a-select>
- </div>
-
- <template v-if="selectedAccountType === $t('label.account')">
- <div class="form__item">
- <p class="form__label"><span class="required">*</span>{{
$t('label.account') }}</p>
- <a-select
- @change="changeAccount"
- v-model:value="selectedAccount"
- showSearch
- optionFilterProp="value"
- :filterOption="(input, option) => {
- return option.value.toLowerCase().indexOf(input.toLowerCase())
>= 0
- }" >
- <a-select-option v-for="account in accounts" :key="account.name"
:value="account.name">
- <span>
- <resource-icon v-if="account && account.icon"
:image="account.icon.base64image" size="1x" style="margin-right: 5px"/>
- <team-outlined v-else style="margin-right: 5px" />
- {{ account.name }}
- </span>
- </a-select-option>
- </a-select>
- <span v-if="accountError" class="required">{{ $t('label.required')
}}</span>
- </div>
- </template>
-
- <template v-else>
- <div class="form__item">
- <p class="form__label"><span class="required">*</span>{{
$t('label.project') }}</p>
- <a-select
- @change="changeProject"
- v-model:value="selectedProject"
- showSearch
- optionFilterProp="label"
- :filterOption="(input, option) => {
- return option.label.toLowerCase().indexOf(input.toLowerCase())
>= 0
- }" >
- <a-select-option v-for="project in projects" :key="project.id"
:value="project.id" :label="project.name">
- <span>
- <resource-icon v-if="project && project.icon"
:image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
- <project-outlined v-else style="margin-right: 5px" />
- {{ project.name }}
- </span>
- </a-select-option>
- </a-select>
- <span v-if="projectError" class="required">{{ $t('label.required')
}}</span>
- </div>
- </template>
+ <ownership-selection @fetch-owner="fetchOwnerOptions"/>
<div class="form__item">
<p class="form__label">{{ $t('label.network') }}</p>
@@ -146,6 +67,7 @@
<script>
import { api } from '@/api'
import ResourceIcon from '@/components/view/ResourceIcon'
+import OwnershipSelection from '@views/compute/wizard/OwnershipSelection'
export default {
name: 'AssignInstance',
@@ -156,7 +78,8 @@ export default {
}
},
components: {
- ResourceIcon
+ ResourceIcon,
+ OwnershipSelection
},
inject: ['parentFetchData'],
data () {
@@ -175,60 +98,13 @@ export default {
loading: false
}
},
- created () {
- this.fetchData()
- },
methods: {
- fetchData () {
- this.loading = true
- api('listDomains', {
- response: 'json',
- listAll: true,
- showicon: true,
- details: 'min'
- }).then(response => {
- this.domains = response.listdomainsresponse.domain || []
- this.selectedDomain = this.domains[0].id
- this.fetchAccounts()
- this.fetchProjects()
- }).catch(error => {
- this.$notifyError(error)
- }).finally(() => {
- this.loading = false
- })
- },
- fetchAccounts () {
- this.loading = true
- api('listAccounts', {
- response: 'json',
- domainId: this.selectedDomain,
- showicon: true,
- state: 'Enabled',
- isrecursive: false
- }).then(response => {
- this.accounts = response.listaccountsresponse.account || []
- }).catch(error => {
- this.$notifyError(error)
- }).finally(() => {
- this.loading = false
- })
- },
- fetchProjects () {
- this.loading = true
- api('listProjects', {
- response: 'json',
- domainId: this.selectedDomain,
- state: 'Active',
- showicon: true,
- details: 'min',
- isrecursive: false
- }).then(response => {
- this.projects = response.listprojectsresponse.project || []
- }).catch(error => {
- this.$notifyError(error)
- }).finally(() => {
- this.loading = false
- })
+ fetchOwnerOptions (selectedOptions) {
+ this.selectedAccountType = selectedOptions.selectedAccountType
+ this.selectedAccount = selectedOptions.selectedAccount
+ this.selectedDomain = selectedOptions.selectedDomain
+ this.selectedProject = selectedOptions.selectedProject
+ this.fetchNetworks()
},
fetchNetworks () {
this.loading = true
@@ -252,23 +128,6 @@ export default {
this.loading = false
})
},
- changeDomain () {
- this.selectedAccount = null
- this.selectedProject = null
- this.selectedNetwork = null
- this.fetchAccounts()
- this.fetchProjects()
- },
- changeAccount () {
- this.selectedProject = null
- this.selectedNetwork = null
- this.fetchNetworks()
- },
- changeProject () {
- this.selectedAccount = null
- this.selectedNetwork = null
- this.fetchNetworks()
- },
closeAction () {
this.$emit('close-action')
},
@@ -356,12 +215,6 @@ export default {
}
}
- .required {
- margin-right: 2px;
- color: red;
- font-size: 0.7rem;
- }
-
.loading {
position: absolute;
top: 0;
diff --git a/ui/src/views/compute/DeployVM.vue
b/ui/src/views/compute/DeployVM.vue
index ef3926bbca0..865ee79d76c 100644
--- a/ui/src/views/compute/DeployVM.vue
+++ b/ui/src/views/compute/DeployVM.vue
@@ -29,6 +29,17 @@
layout="vertical"
>
<a-steps direction="vertical" size="small">
+ <a-step
+ v-if="!isNormalUserOrProject"
+ :title="this.$t('label.assign.instance.another')">
+ <template #description>
+ <div style="margin-top: 15px">
+ {{ $t('label.assigning.vms') }}
+ <ownership-selection
+ @fetch-owner="fetchOwnerOptions"/>
+ </div>
+ </template>
+ </a-step>
<a-step :title="$t('label.select.deployment.infrastructure')"
status="process">
<template #description>
<div style="margin-top: 15px">
@@ -848,6 +859,7 @@ import { mixin, mixinDevice } from '@/utils/mixin.js'
import store from '@/store'
import eventBus from '@/config/eventBus'
+import OwnershipSelection from '@views/compute/wizard/OwnershipSelection'
import InfoCard from '@/components/view/InfoCard'
import ResourceIcon from '@/components/view/ResourceIcon'
import ComputeOfferingSelection from
'@views/compute/wizard/ComputeOfferingSelection'
@@ -868,6 +880,7 @@ import InstanceNicsNetworkSelectListView from
'@/components/view/InstanceNicsNet
export default {
name: 'Wizard',
components: {
+ OwnershipSelection,
SshKeyPairSelection,
UserDataSelection,
NetworkConfiguration,
@@ -965,6 +978,11 @@ export default {
hosts: false,
groups: false
},
+ owner: {
+ 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
+ },
instanceConfig: {},
template: {},
defaultBootType: '',
@@ -1056,11 +1074,21 @@ export default {
isNormalAndDomainUser () {
return ['DomainAdmin',
'User'].includes(this.$store.getters.userInfo.roletype)
},
+ isNormalUserOrProject () {
+ return ['User'].includes(this.$store.getters.userInfo.roletype) ||
store.getters.project.id
+ },
diskSize () {
- const rootDiskSize = _.get(this.instanceConfig, 'rootdisksize', 0)
- const customDiskSize = _.get(this.instanceConfig, 'size', 0)
+ let dataDiskSize
+ let rootDiskSize = _.get(this.instanceConfig, 'rootdisksize', 0)
const diskOfferingDiskSize = _.get(this.diskOffering, 'disksize', 0)
- const dataDiskSize = diskOfferingDiskSize > 0 ? diskOfferingDiskSize :
customDiskSize
+ const customDiskSize = _.get(this.instanceConfig, 'size', 0)
+
+ if (this.vm.isoid != null) {
+ rootDiskSize = diskOfferingDiskSize > 0 ? diskOfferingDiskSize :
customDiskSize
+ } else {
+ dataDiskSize = diskOfferingDiskSize > 0 ? diskOfferingDiskSize :
customDiskSize
+ }
+
const size = []
if (rootDiskSize > 0) {
size.push(`${rootDiskSize} GB (Root)`)
@@ -1079,6 +1107,9 @@ export default {
list: 'listServiceOfferings',
options: {
zoneid: _.get(this.zone, 'id'),
+ projectid: this.owner.projectid,
+ domainid: this.owner.domainid,
+ account: this.owner.account,
issystem: false,
page: 1,
pageSize: 10,
@@ -1089,6 +1120,9 @@ export default {
list: 'listDiskOfferings',
options: {
zoneid: _.get(this.zone, 'id'),
+ projectid: this.owner.projectid,
+ domainid: this.owner.domainid,
+ account: this.owner.account,
page: 1,
pageSize: 10,
keyword: undefined
@@ -1111,6 +1145,9 @@ export default {
options: {
page: 1,
pageSize: 10,
+ account: this.owner.account,
+ domainid: this.owner.domainid,
+ projectid: this.owner.projectid,
keyword: undefined,
listall: false
}
@@ -1138,9 +1175,9 @@ export default {
options: {
zoneid: _.get(this.zone, 'id'),
canusefordeploy: true,
- projectid: store.getters.project ? store.getters.project.id : null,
- domainid: store.getters.project && store.getters.project.id ? null
: store.getters.userInfo.domainid,
- account: store.getters.project && store.getters.project.id ? null
: store.getters.userInfo.account,
+ projectid: store.getters.project.id || this.owner.projectid,
+ domainid: store.getters.project.id ? null : this.owner.domainid,
+ account: store.getters.project.id ? null : this.owner.account,
page: 1,
pageSize: 10,
keyword: undefined,
@@ -1303,7 +1340,7 @@ export default {
return tabList
},
showSecurityGroupSection () {
- return (this.networks.length > 0 && this.zone.securitygroupsenabled) ||
(this.zone && this.zone.networktype === 'Basic')
+ return (this.networks.length > 0 && this.zone?.securitygroupsenabled) ||
(this.zone?.networktype === 'Basic')
},
isUserAllowedToListSshKeys () {
return Boolean('listSSHKeyPairs' in this.$store.getters.apis)
@@ -1372,7 +1409,7 @@ export default {
this.diskOffering = _.find(this.options.diskOfferings, (option) =>
option.id === instanceConfig.diskofferingid)
}
- this.zone = _.find(this.options.zones, (option) => option.id ===
instanceConfig.zoneid)
+ this.zone = _.find(this.options.zones, (option) => option.id ===
this.instanceConfig.zoneid)
this.affinityGroups = _.filter(this.options.affinityGroups, (option)
=> _.includes(instanceConfig.affinitygroupids, option.id))
this.networks =
this.getSelectedNetworksWithExistingConfig(_.filter(this.options.networks,
(option) => _.includes(instanceConfig.networkids, option.id)))
@@ -1685,8 +1722,8 @@ export default {
fetchInstaceGroups () {
this.options.instanceGroups = []
api('listInstanceGroups', {
- account: this.$store.getters.userInfo.account,
- domainid: this.$store.getters.userInfo.domainid,
+ account: this.$store.getters.project?.id ? null :
this.$store.getters.userInfo.account,
+ domainid: this.$store.getters.project?.id ? null :
this.$store.getters.userInfo.domainid,
listall: true
}).then(response => {
const groups = response.listinstancegroupsresponse.instancegroup || []
@@ -1830,7 +1867,7 @@ export default {
this.userDataParams = []
api('listUserData', { id: id }).then(json => {
const resp = json?.listuserdataresponse?.userdata || []
- if (resp) {
+ if (resp[0]) {
var params = resp[0].params
if (params) {
var dataParams = params.split(',')
@@ -2086,6 +2123,14 @@ export default {
deployVmData.bootintosetup = values.bootintosetup
}
+ if (this.owner.account) {
+ deployVmData.account = this.owner.account
+ deployVmData.domainid = this.owner.domainid
+ } else if (this.owner.projectid) {
+ deployVmData.domainid = this.owner.domainid
+ deployVmData.projectid = this.owner.projectid
+ }
+
const title = this.$t('label.launch.vm')
const description = values.name || ''
const password = this.$t('label.password')
@@ -2178,6 +2223,29 @@ export default {
}
})
},
+ fetchOwnerOptions (OwnerOptions) {
+ this.owner = {
+ projectid: null,
+ domainid: store.getters.userInfo.domainid,
+ account: store.getters.userInfo.account
+ }
+ if (OwnerOptions.selectedAccountType === this.$t('label.account')) {
+ if (!OwnerOptions.selectedAccount) {
+ return
+ }
+ this.owner.account = OwnerOptions.selectedAccount
+ this.owner.domainid = OwnerOptions.selectedDomain
+ this.owner.projectid = null
+ } else if (OwnerOptions.selectedAccountType ===
this.$t('label.project')) {
+ if (!OwnerOptions.selectedProject) {
+ return
+ }
+ this.owner.account = null
+ this.owner.domainid = null
+ this.owner.projectid = OwnerOptions.selectedProject
+ }
+ this.resetData()
+ },
fetchZones (zoneId, listZoneAllow) {
this.zones = []
return new Promise((resolve) => {
@@ -2275,6 +2343,9 @@ export default {
args.pageSize = args.pageSize || 10
}
args.zoneid = _.get(this.zone, 'id')
+ args.account = store.getters.project?.id ? null : this.owner.account
+ args.domainid = store.getters.project?.id ? null : this.owner.domainid
+ args.projectid = store.getters.project?.id || this.owner.projectid
args.templatefilter = templateFilter
args.details = 'all'
args.showicon = 'true'
@@ -2531,6 +2602,7 @@ export default {
}
},
resetFromTemplateConfiguration () {
+ this.deleteFrom(this.instanceConfig, ['disksize', 'rootdisksize'])
this.deleteFrom(this.params.serviceOfferings.options, ['templateid',
'cpuspeed', 'cpunumber', 'memory'])
this.deleteFrom(this.dataPreFill, ['cpuspeed', 'cpunumber', 'memory'])
this.handleSearchFilter('serviceOfferings', {
diff --git a/ui/src/views/compute/wizard/OwnershipSelection.vue
b/ui/src/views/compute/wizard/OwnershipSelection.vue
new file mode 100644
index 00000000000..61c30f340ac
--- /dev/null
+++ b/ui/src/views/compute/wizard/OwnershipSelection.vue
@@ -0,0 +1,257 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+<template>
+ <a-form layout="vertical" >
+ <a-form-item :label="$t('label.owner.type')">
+ <a-select
+ @change="changeDomain"
+ v-model:value="selectedAccountType"
+ defaultValue="account"
+ autoFocus
+ showSearch
+ optionFilterProp="label"
+ :filterOption="
+ (input, option) => {
+ return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+ }
+ "
+ >
+ <a-select-option :value="$t('label.account')">{{ $t('label.account')
}}</a-select-option>
+ <a-select-option :value="$t('label.project')">{{ $t('label.project')
}}</a-select-option>
+ </a-select>
+ </a-form-item>
+ <a-form-item :label="$t('label.domain')" required>
+ <a-select
+ @change="changeDomain"
+ v-model:value="selectedDomain"
+ showSearch
+ optionFilterProp="label"
+ :filterOption="
+ (input, option) => {
+ return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
+ }
+ "
+ >
+ <a-select-option
+ v-for="domain in domains"
+ :key="domain.name"
+ :value="domain.id"
+ :label="domain.path || domain.name || domain.description"
+ >
+ <span>
+ <resource-icon
+ v-if="domain && domain.icon"
+ :image="domain.icon.base64image"
+ size="1x"
+ style="margin-right: 5px"
+ />
+ <block-outlined v-else />
+ {{ domain.path || domain.name || domain.description }}
+ </span>
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+
+ <template v-if="selectedAccountType === $t('label.account')">
+ <a-form-item :label="$t('label.account')" required>
+ <a-select
+ @change="emitChangeEvent"
+ v-model:value="selectedAccount"
+ showSearch
+ optionFilterProp="label"
+ :filterOption="
+ (input, option) => {
+ return option.label.toLowerCase().indexOf(input.toLowerCase())
>= 0
+ }
+ "
+ >
+ <a-select-option v-for="account in accounts" :key="account.name"
:value="account.name">
+ <span>
+ <resource-icon
+ v-if="account && account.icon"
+ :image="account.icon.base64image"
+ size="1x"
+ style="margin-right: 5px"
+ />
+ <team-outlined v-else />
+ {{ account.name }}
+ </span>
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ </template>
+
+ <template v-else>
+ <a-form-item :label="$t('label.project')" required>
+ <a-select
+ @change="emitChangeEvent"
+ v-model:value="selectedProject"
+ showSearch
+ optionFilterProp="label"
+ :filterOption="
+ (input, option) => {
+ return option.label.toLowerCase().indexOf(input.toLowerCase())
>= 0
+ }
+ "
+ >
+ <a-select-option v-for="project in projects" :key="project.id"
:value="project.id" :label="project.name">
+ <span>
+ <resource-icon
+ v-if="project && project.icon"
+ :image="project.icon.base64image"
+ size="1x"
+ style="margin-right: 5px"
+ />
+ <project-outlined v-else />
+ {{ project.name }}
+ </span>
+ </a-select-option>
+ </a-select>
+ </a-form-item>
+ </template>
+ </a-form>
+</template>
+
+<script>
+import { api } from '@/api'
+import ResourceIcon from '@/components/view/ResourceIcon.vue'
+
+export default {
+ name: 'OwnershipSelection',
+ components: { ResourceIcon },
+ data () {
+ return {
+ domains: [],
+ accounts: [],
+ projects: [],
+ selectedAccountType: this.$store.getters.project?.id ?
this.$t('label.project') : this.$t('label.account'),
+ selectedDomain: null,
+ selectedAccount: null,
+ selectedProject: null,
+ loading: false
+ }
+ },
+ props: {
+ override: {
+ type: Object
+ }
+ },
+ created () {
+ this.fetchData()
+ },
+ methods: {
+ fetchData () {
+ this.loading = true
+ api('listDomains', {
+ response: 'json',
+ listAll: true,
+ showicon: true,
+ details: 'min'
+ })
+ .then((response) => {
+ this.domains = response.listdomainsresponse.domain
+ if (this.override) {
+ this.domains = this.domains.filter(item =>
this.override.domains.has(item.id))
+ }
+ if (this.domains.length === 0) {
+ this.selectedDomain = null
+ this.selectedProject = null
+ this.selectedAccount = null
+ return
+ }
+ const domainIds = this.domains?.map(domain => domain.id)
+ const ownerDomainId = this.$store.getters.project?.domainid ||
this.$store.getters.userInfo.domainid
+ this.selectedDomain = domainIds?.includes(ownerDomainId) ?
ownerDomainId : this.domains?.[0]?.id
+ this.changeDomain()
+ })
+ .catch((error) => {
+ this.$notifyError(error)
+ })
+ .finally(() => {
+ this.loading = false
+ })
+ },
+ fetchAccounts () {
+ this.loading = true
+ api('listAccounts', {
+ response: 'json',
+ domainId: this.selectedDomain,
+ showicon: true,
+ state: 'Enabled',
+ isrecursive: false
+ })
+ .then((response) => {
+ this.accounts = response.listaccountsresponse.account || []
+ if (this.override?.accounts && this.accounts) {
+ this.accounts = this.accounts.filter(item =>
this.override.accounts.has(item.name))
+ }
+ const accountNames = this.accounts.map(account => account.name)
+ if (this.selectedDomain === this.$store.getters.userInfo.domainid &&
accountNames.includes(this.$store.getters.userInfo.account)) {
+ this.selectedAccount = this.$store.getters.userInfo.account
+ } else {
+ this.selectedAccount = this.accounts?.[0]?.name
+ }
+ this.selectedProject = null
+ this.emitChangeEvent()
+ })
+ .catch((error) => {
+ this.$notifyError(error)
+ })
+ .finally(() => {
+ this.loading = false
+ })
+ },
+ fetchProjects () {
+ this.loading = true
+ api('listProjects', {
+ response: 'json',
+ domainId: this.selectedDomain,
+ state: 'Active',
+ showicon: true,
+ details: 'min',
+ isrecursive: false
+ })
+ .then((response) => {
+ this.projects = response.listprojectsresponse.project
+ if (this.override?.projects && this.projects) {
+ this.projects = this.projects.filter(item =>
this.override.projects.has(item.id))
+ }
+ this.selectedProject = this.projects?.map(project =>
project.id)?.includes(this.$store.getters.project?.id) ?
this.$store.getters.project?.id : this.projects?.[0]?.id
+ this.selectedAccount = null
+ this.emitChangeEvent()
+ })
+ .catch((error) => {
+ this.$notifyError(error)
+ })
+ .finally(() => {
+ this.loading = false
+ })
+ },
+ changeDomain () {
+ if (this.selectedAccountType === this.$t('label.account')) {
+ this.fetchAccounts()
+ } else {
+ this.fetchProjects()
+ }
+ },
+ emitChangeEvent () {
+ this.$emit('fetch-owner', this)
+ }
+ }
+}
+</script>
diff --git a/ui/src/views/network/CreateIsolatedNetworkForm.vue
b/ui/src/views/network/CreateIsolatedNetworkForm.vue
index 919821576e0..0c81a69057c 100644
--- a/ui/src/views/network/CreateIsolatedNetworkForm.vue
+++ b/ui/src/views/network/CreateIsolatedNetworkForm.vue
@@ -66,44 +66,7 @@
</a-select-option>
</a-select>
</a-form-item>
- <a-form-item ref="domainid" name="domainid"
v-if="isAdminOrDomainAdmin()">
- <template #label>
- <tooltip-label :title="$t('label.domainid')"
:tooltip="apiParams.domainid.description"/>
- </template>
- <a-select
- v-model:value="form.domainid"
- showSearch
- optionFilterProp="label"
- :filterOption="(input, option) => {
- return option.label.toLowerCase().indexOf(input.toLowerCase())
>= 0
- }"
- :loading="domain.loading"
- :placeholder="apiParams.domainid.description"
- @change="val => { handleDomainChange(domains[val]) }">
- <a-select-option v-for="(opt, optIndex) in domains"
:key="optIndex" :label="opt.path || opt.name || opt.description">
- {{ opt.path || opt.name || opt.description }}
- </a-select-option>
- </a-select>
- </a-form-item>
- <a-form-item ref="account" name="account" v-if="accountVisible">
- <template #label>
- <tooltip-label :title="$t('label.account')"
:tooltip="apiParams.account.description"/>
- </template>
- <a-select
- v-model:value="form.account"
- showSearch
- optionFilterProp="label"
- :filterOption="(input, option) => {
- return
option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
- }"
- :loading="accountLoading"
- :placeholder="apiParams.account.description"
- @change="val => { handleAccountChange(accounts[val]) }">
- <a-select-option v-for="(opt, optIndex) in accounts"
:key="optIndex">
- {{ opt.name || opt.description }}
- </a-select-option>
- </a-select>
- </a-form-item>
+ <ownership-selection v-if="isAdminOrDomainAdmin()"
@fetch-owner="fetchOwnerOptions"/>
<a-form-item
ref="networkdomain"
name="networkdomain"
@@ -299,28 +262,6 @@
v-model:value="form.sourcenatipaddress"
:placeholder="apiParams.sourcenatipaddress?.description"/>
</a-form-item>
- <a-form-item
- ref="networkdomain"
- name="networkdomain"
- v-if="!isObjectEmpty(selectedNetworkOffering) &&
!selectedNetworkOffering.forvpc">
- <template #label>
- <tooltip-label :title="$t('label.networkdomain')"
:tooltip="apiParams.networkdomain.description"/>
- </template>
- <a-input
- v-model:value="form.networkdomain"
- :placeholder="apiParams.networkdomain.description"/>
- </a-form-item>
- <a-form-item
- ref="account"
- name="account"
- v-if="accountVisible">
- <template #label>
- <tooltip-label :title="$t('label.account')"
:tooltip="apiParams.account.description"/>
- </template>
- <a-input
- v-model:value="form.account"
- :placeholder="apiParams.account.description"/>
- </a-form-item>
<div :span="24" class="action-button">
<a-button
:loading="actionLoading"
@@ -349,13 +290,15 @@ import { isAdmin, isAdminOrDomainAdmin } from '@/role'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
+import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue'
export default {
name: 'CreateIsolatedNetworkForm',
mixins: [mixinForm],
components: {
TooltipLabel,
- ResourceIcon
+ ResourceIcon,
+ OwnershipSelection
},
props: {
loading: {
@@ -374,13 +317,9 @@ export default {
data () {
return {
actionLoading: false,
- domains: [],
- domain: { loading: false },
- selectedDomain: {},
+ owner: {},
accountVisible: isAdminOrDomainAdmin(),
- accounts: [],
accountLoading: false,
- selectedAccount: {},
zones: [],
zoneLoading: false,
selectedZone: {},
@@ -410,12 +349,6 @@ export default {
this.apiParams = this.$getApiParams('createNetwork')
},
created () {
- this.domains = [
- {
- id: '-1',
- name: ' '
- }
- ]
this.initForm()
this.fetchData()
},
@@ -449,7 +382,6 @@ export default {
})
},
fetchData () {
- this.fetchDomainData()
this.fetchZoneData()
this.allowSettingMTU()
},
@@ -496,44 +428,31 @@ export default {
this.publicMtuMax = zone?.routerpublicinterfacemaxmtu || 1500
this.updateVPCCheckAndFetchNetworkOfferingData()
},
- fetchDomainData () {
- if ('listDomains' in this.$store.getters.apis) {
- this.domain.loading = true
- this.loadMore('listDomains', 1, this.domain)
+ fetchOwnerOptions (OwnerOptions) {
+ this.owner = {
+ projectid: null,
+ domainid: this.$store.getters.userInfo.domainid,
+ account: this.$store.getters.userInfo.account
}
- },
- loadMore (apiToCall, page, sema) {
- const params = {}
- params.listAll = true
- params.details = 'min'
- params.pagesize = 100
- params.page = page
- var count
- api(apiToCall, params).then(json => {
- const listDomains = json.listdomainsresponse.domain
- count = json.listdomainsresponse.count
- this.domains = this.domains.concat(listDomains)
- }).finally(() => {
- if (count <= this.domains.length) {
- sema.loading = false
- } else {
- this.loadMore(apiToCall, page + 1, sema)
+ if (OwnerOptions.selectedAccountType === this.$t('label.account')) {
+ if (!OwnerOptions.selectedAccount) {
+ return
}
- this.form.domainid = 0
- this.handleDomainChange(this.domains[0])
- })
- },
- handleDomainChange (domain) {
- this.selectedDomain = domain
- this.accountVisible = domain.id !== '-1'
+ this.owner.account = OwnerOptions.selectedAccount
+ this.owner.domainid = OwnerOptions.selectedDomain
+ this.owner.projectid = null
+ } else if (OwnerOptions.selectedAccountType ===
this.$t('label.project')) {
+ if (!OwnerOptions.selectedProject) {
+ return
+ }
+ this.owner.account = null
+ this.owner.domainid = null
+ this.owner.projectid = OwnerOptions.selectedProject
+ }
if (isAdminOrDomainAdmin()) {
this.updateVPCCheckAndFetchNetworkOfferingData()
- this.fetchAccounts()
}
},
- handleAccountChange (account) {
- this.selectedAccount = account
- },
updateVPCCheckAndFetchNetworkOfferingData () {
if (this.vpc !== null) { // from VPC section
this.fetchNetworkOfferingData(true)
@@ -562,8 +481,8 @@ export default {
guestiptype: 'Isolated',
state: 'Enabled'
}
- if (isAdminOrDomainAdmin() && this.selectedDomain.id !== '-1') { //
domain is visible only for admins
- params.domainid = this.selectedDomain.id
+ if (isAdminOrDomainAdmin() && this.owner.domainid !== '-1') { // domain
is visible only for admins
+ params.domainid = this.owner.domainid
}
if (!isAdmin()) { // normal user is not aware of the VLANs in the
system, so normal user is not allowed to create network with network offerings
whose specifyvlan = true
params.specifyvlan = false
@@ -613,31 +532,6 @@ export default {
}
})
},
- fetchAccounts () {
- this.accountLoading = true
- var params = {}
- if (isAdminOrDomainAdmin() && this.selectedDomain.id !== '-1') { //
domain is visible only for admins
- params.domainid = this.selectedDomain.id
- }
- this.accounts = [
- {
- id: '-1',
- name: ' '
- }
- ]
- this.selectedAccount = {}
- api('listAccounts', params).then(json => {
- const listAccounts = json.listaccountsresponse.account || []
- this.accounts = this.accounts.concat(listAccounts)
- }).catch(error => {
- this.$notifyError(error)
- }).finally(() => {
- this.accountLoading = false
- if (this.arrayHasItems(this.accounts)) {
- this.form.account = null
- }
- })
- },
handleSubmit () {
if (this.actionLoading) return
this.formRef.value.validate().then(() => {
@@ -666,12 +560,15 @@ export default {
if ('vpcid' in values) {
params.vpcid = this.selectedVpc.id
}
- if ('domainid' in values && values.domainid > 0) {
- params.domainid = this.selectedDomain.id
- if (this.isValidTextValueForKey(values, 'account') &&
this.selectedAccount.id !== '-1') {
- params.account = this.selectedAccount.name
- }
+
+ if (this.owner.account) {
+ params.account = this.owner.account
+ params.domainid = this.owner.domainid
+ } else if (this.owner.projectid) {
+ params.domainid = this.owner.domainid
+ params.projectid = this.owner.projectid
}
+
api('createNetwork', params).then(json => {
this.$notification.success({
message: 'Network',
diff --git a/ui/src/views/network/CreateL2NetworkForm.vue
b/ui/src/views/network/CreateL2NetworkForm.vue
index 6bb87e178f3..41d2ec8c2f2 100644
--- a/ui/src/views/network/CreateL2NetworkForm.vue
+++ b/ui/src/views/network/CreateL2NetworkForm.vue
@@ -73,48 +73,7 @@
</a-select-option>
</a-select>
</a-form-item>
- <a-form-item v-if="isAdminOrDomainAdmin()" name="domainid"
ref="domainid">
- <template #label>
- <tooltip-label :title="$t('label.domainid')"
:tooltip="apiParams.domainid.description"/>
- </template>
- <a-select
- v-model:value="form.domainid"
- showSearch
- optionFilterProp="label"
- :filterOption="(input, option) => {
- return option.label.toLowerCase().indexOf(input.toLowerCase())
>= 0
- }"
- :loading="domainLoading"
- :placeholder="apiParams.domainid.description"
- @change="val => { handleDomainChange(domains[val]) }">
- <a-select-option v-for="(opt, optIndex) in this.domains"
:key="optIndex" :label="opt.path || opt.name || opt.description">
- <span>
- <resource-icon v-if="opt && opt.icon"
:image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
- <block-outlined v-else-if="optIndex !== 0"
style="margin-right: 5px" />
- {{ opt.path || opt.name || opt.description }}
- </span>
- </a-select-option>
- </a-select>
- </a-form-item>
- <a-form-item v-if="accountVisible" name="account" ref="account">
- <template #label>
- <tooltip-label :title="$t('label.account')"
:tooltip="apiParams.account.description"/>
- </template>
- <a-select
- v-model:value="form.account"
- showSearch
- optionFilterProp="label"
- :filterOption="(input, option) => {
- return
option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
- }"
- :loading="accountLoading"
- :placeholder="apiParams.account.description"
- @change="val => { handleAccountChange(accounts[val]) }">
- <a-select-option v-for="(opt, optIndex) in accounts"
:key="optIndex">
- {{ opt.name || opt.description }}
- </a-select-option>
- </a-select>
- </a-form-item>
+ <ownership-selection v-if="isAdminOrDomainAdmin()"
@fetch-owner="fetchOwnerOptions"/>
<a-form-item name="networkofferingid" ref="networkofferingid">
<template #label>
<tooltip-label :title="$t('label.networkofferingid')"
:tooltip="apiParams.networkofferingid.description"/>
@@ -217,11 +176,13 @@ import { isAdmin, isAdminOrDomainAdmin } from '@/role'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
+import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue'
export default {
name: 'CreateL2NetworkForm',
mixins: [mixinForm],
components: {
+ OwnershipSelection,
TooltipLabel,
ResourceIcon
},
@@ -242,13 +203,8 @@ export default {
data () {
return {
actionLoading: false,
- domains: [],
- domainLoading: false,
- selectedDomain: {},
+ owner: {},
accountVisible: isAdminOrDomainAdmin(),
- accounts: [],
- accountLoading: false,
- selectedAccount: {},
zones: [],
zoneLoading: false,
selectedZone: {},
@@ -271,12 +227,6 @@ export default {
this.apiParams = this.$getApiParams('createNetwork')
},
created () {
- this.domains = [
- {
- id: '-1',
- name: ' '
- }
- ]
this.initForm()
this.fetchData()
},
@@ -294,7 +244,6 @@ export default {
})
},
fetchData () {
- this.fetchDomainData()
this.fetchZoneData()
},
isAdminOrDomainAdmin () {
@@ -339,32 +288,31 @@ export default {
this.isNsxEnabled = zone?.isnsxenabled || false
this.updateVPCCheckAndFetchNetworkOfferingData()
},
- fetchDomainData () {
- const params = {}
- params.listAll = true
- params.showicon = true
- params.details = 'min'
- this.domainLoading = true
- api('listDomains', params).then(json => {
- const listDomains = json.listdomainsresponse.domain
- this.domains = this.domains.concat(listDomains)
- }).finally(() => {
- this.domainLoading = false
- this.form.domainid = 0
- this.handleDomainChange(this.domains[0])
- })
- },
- handleDomainChange (domain) {
- this.selectedDomain = domain
- this.accountVisible = domain.id !== '-1'
+ fetchOwnerOptions (OwnerOptions) {
+ this.owner = {
+ projectid: null,
+ domainid: this.$store.getters.userInfo.domainid,
+ account: this.$store.getters.userInfo.account
+ }
+ if (OwnerOptions.selectedAccountType === this.$t('label.account')) {
+ if (!OwnerOptions.selectedAccount) {
+ return
+ }
+ this.owner.account = OwnerOptions.selectedAccount
+ this.owner.domainid = OwnerOptions.selectedDomain
+ this.owner.projectid = null
+ } else if (OwnerOptions.selectedAccountType ===
this.$t('label.project')) {
+ if (!OwnerOptions.selectedProject) {
+ return
+ }
+ this.owner.account = null
+ this.owner.domainid = null
+ this.owner.projectid = OwnerOptions.selectedProject
+ }
if (isAdminOrDomainAdmin()) {
this.updateVPCCheckAndFetchNetworkOfferingData()
- this.fetchAccounts()
}
},
- handleAccountChange (account) {
- this.selectedAccount = account
- },
updateVPCCheckAndFetchNetworkOfferingData () {
if (this.vpc !== null) { // from VPC section
this.fetchNetworkOfferingData(true)
@@ -393,8 +341,8 @@ export default {
guestiptype: 'L2',
state: 'Enabled'
}
- if (isAdminOrDomainAdmin() && this.selectedDomain.id !== '-1') { //
domain is visible only for admins
- params.domainid = this.selectedDomain.id
+ if (isAdminOrDomainAdmin() && this.owner.domainid !== '-1') { // domain
is visible only for admins
+ params.domainid = this.owner.domainid
}
if (!isAdmin()) { // normal user is not aware of the VLANs in the
system, so normal user is not allowed to create network with network offerings
whose specifyvlan = true
params.specifyvlan = false
@@ -417,31 +365,6 @@ export default {
handleNetworkOfferingChange (networkOffering) {
this.selectedNetworkOffering = networkOffering
},
- fetchAccounts () {
- this.accountLoading = true
- var params = {}
- if (isAdminOrDomainAdmin() && this.selectedDomain.id !== '-1') { //
domain is visible only for admins
- params.domainid = this.selectedDomain.id
- }
- this.accounts = [
- {
- id: '-1',
- name: ' '
- }
- ]
- this.selectedAccount = {}
- api('listAccounts', params).then(json => {
- const listAccounts = json.listaccountsresponse.account || []
- this.accounts = this.accounts.concat(listAccounts)
- }).catch(error => {
- this.$notifyError(error)
- }).finally(() => {
- this.accountLoading = false
- if (this.arrayHasItems(this.accounts)) {
- this.form.account = null
- }
- })
- },
handleSubmit (e) {
if (this.actionLoading) return
this.formRef.value.validate().then(() => {
@@ -460,12 +383,15 @@ export default {
if (this.isValidValueForKey(values, 'bypassvlanoverlapcheck')) {
params.bypassvlanoverlapcheck = values.bypassvlanoverlapcheck
}
- if ('domainid' in values && values.domainid > 0) {
- params.domainid = this.selectedDomain.id
- if (this.isValidTextValueForKey(values, 'account') &&
this.selectedAccount.id !== '-1') {
- params.account = this.selectedAccount.name
- }
+
+ if (this.owner.account) {
+ params.account = this.owner.account
+ params.domainid = this.owner.domainid
+ } else if (this.owner.projectid) {
+ params.domainid = this.owner.domainid
+ params.projectid = this.owner.projectid
}
+
if (this.isValidValueForKey(values, 'isolatedpvlantype') &&
values.isolatedpvlantype !== 'none') {
params.isolatedpvlantype = values.isolatedpvlantype
if (this.isValidValueForKey(values, 'isolatedpvlan')) {