mirror of https://github.com/apache/cloudstack.git
Compare commits
9 Commits
f837d954e3
...
33200fcb69
| Author | SHA1 | Date |
|---|---|---|
|
|
33200fcb69 | |
|
|
cd5bb09d0d | |
|
|
b5e9178078 | |
|
|
4676fb1090 | |
|
|
4fc90c123d | |
|
|
99dca0862a | |
|
|
f317e9cc16 | |
|
|
c7d282cd2e | |
|
|
10b60c27a0 |
|
|
@ -2527,7 +2527,7 @@
|
|||
"label.vnf.app.action.reinstall": "Reinstall VNF Appliance",
|
||||
"label.vnf.cidr.list": "CIDR from which access to the VNF appliance's Management interface should be allowed from",
|
||||
"label.vnf.cidr.list.tooltip": "the CIDR list to forward traffic from to the VNF management interface. Multiple entries must be separated by a single comma character (,). The default value is 0.0.0.0/0.",
|
||||
"label.vnf.configure.management": "Configure Firewall and Port Forwarding rules for VNF's management interfaces",
|
||||
"label.vnf.configure.management": "Configure network rules for VNF's management interfaces",
|
||||
"label.vnf.configure.management.tooltip": "True by default, security group or network rules (source nat and firewall rules) will be configured for VNF management interfaces. False otherwise. Learn what rules are configured at http://docs.cloudstack.apache.org/en/latest/adminguide/networking/vnf_templates_appliances.html#deploying-vnf-appliances",
|
||||
"label.vnf.detail.add": "Add VNF detail",
|
||||
"label.vnf.detail.remove": "Remove VNF detail",
|
||||
|
|
|
|||
|
|
@ -18,52 +18,44 @@
|
|||
<template>
|
||||
<div class="form">
|
||||
<div class="form__item" :class="{'error': domainError}">
|
||||
<a-spin :spinning="domainsLoading">
|
||||
<p class="form__label">{{ $t('label.domain') }}<span class="required">*</span></p>
|
||||
<p class="required required-label">{{ $t('label.required') }}</p>
|
||||
<a-select
|
||||
style="width: 100%"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
@change="handleChangeDomain"
|
||||
v-focus="true"
|
||||
v-model:value="domainId">
|
||||
<a-select-option
|
||||
v-for="(domain, index) in domainsList"
|
||||
:value="domain.id"
|
||||
:key="index"
|
||||
:label="domain.path || domain.name || domain.description">
|
||||
{{ domain.path || domain.name || domain.description }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-spin>
|
||||
</div>
|
||||
<div class="form__item" v-if="accountsList">
|
||||
<p class="form__label">{{ $t('label.account') }}</p>
|
||||
<a-select
|
||||
<p class="form__label">{{ $t('label.domain') }}<span class="required">*</span></p>
|
||||
<p class="required required-label">{{ $t('label.required') }}</p>
|
||||
<infinite-scroll-select
|
||||
style="width: 100%"
|
||||
@change="handleChangeAccount"
|
||||
showSearch
|
||||
optionFilterProp="value"
|
||||
:filterOption="(input, option) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option v-for="(account, index) in accountsList" :value="account.name" :key="index">
|
||||
{{ account.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
v-model:value="domainId"
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
v-focus="true"
|
||||
@change-option-value="handleChangeDomain" />
|
||||
</div>
|
||||
<div class="form__item">
|
||||
<p class="form__label">{{ $t('label.account') }}</p>
|
||||
<infinite-scroll-select
|
||||
style="width: 100%"
|
||||
v-model:value="selectedAccount"
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="name"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
@change-option-value="handleChangeAccount" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'DedicateDomain',
|
||||
components: {
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
props: {
|
||||
error: {
|
||||
type: Boolean,
|
||||
|
|
@ -72,59 +64,48 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
domainsLoading: false,
|
||||
domainId: null,
|
||||
accountsList: null,
|
||||
domainsList: null,
|
||||
selectedAccount: null,
|
||||
domainError: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listall: true,
|
||||
details: 'min'
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
if (!this.domainId) {
|
||||
return {
|
||||
listall: true,
|
||||
showicon: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
showicon: true,
|
||||
domainid: this.domainId
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
error () {
|
||||
this.domainError = this.error
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
this.domainsLoading = true
|
||||
api('listDomains', {
|
||||
listAll: true,
|
||||
details: 'min'
|
||||
}).then(response => {
|
||||
this.domainsList = response.listdomainsresponse.domain
|
||||
|
||||
if (this.domainsList[0]) {
|
||||
this.domainId = this.domainsList[0].id
|
||||
this.handleChangeDomain(this.domainId)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.domainsLoading = false
|
||||
})
|
||||
},
|
||||
fetchAccounts () {
|
||||
api('listAccounts', {
|
||||
domainid: this.domainId
|
||||
}).then(response => {
|
||||
this.accountsList = response.listaccountsresponse.account || []
|
||||
if (this.accountsList && this.accountsList.length === 0) {
|
||||
this.handleChangeAccount(null)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
})
|
||||
},
|
||||
handleChangeDomain (e) {
|
||||
this.$emit('domainChange', e)
|
||||
handleChangeDomain (domainId) {
|
||||
this.domainId = domainId
|
||||
this.selectedAccount = null
|
||||
this.$emit('domainChange', domainId)
|
||||
this.domainError = false
|
||||
this.fetchAccounts()
|
||||
},
|
||||
handleChangeAccount (e) {
|
||||
this.$emit('accountChange', e)
|
||||
handleChangeAccount (accountName) {
|
||||
this.selectedAccount = accountName
|
||||
this.$emit('accountChange', accountName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@
|
|||
- optionValueKey (String, optional): Property to use as the value for options (e.g., 'name'). Default is 'id'
|
||||
- optionLabelKey (String, optional): Property to use as the label for options (e.g., 'name'). Default is 'name'
|
||||
- defaultOption (Object, optional): Preselected object to include initially
|
||||
- allowClear (Boolean, optional): Whether to allow clearing the selection. Default is false
|
||||
- showIcon (Boolean, optional): Whether to show icon for the options. Default is true
|
||||
- defaultIcon (String, optional): Icon to be shown when there is no resource icon for the option. Default is 'cloud-outlined'
|
||||
- selectFirstOption (Boolean, optional): Whether to automatically select the first option when options are loaded. Default is false
|
||||
|
||||
Events:
|
||||
- @change-option-value (Function): Emits the selected option value(s) when value(s) changes. Do not use @change as it will give warnings and may not work
|
||||
|
|
@ -58,6 +60,7 @@
|
|||
:filter-option="false"
|
||||
:loading="loading"
|
||||
show-search
|
||||
:allowClear="allowClear"
|
||||
placeholder="Select"
|
||||
@search="onSearchTimed"
|
||||
@popupScroll="onScroll"
|
||||
|
|
@ -75,9 +78,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-select-option v-for="option in options" :key="option.id" :value="option[optionValueKey]">
|
||||
<a-select-option v-for="option in selectableOptions" :key="option.id" :value="option[optionValueKey]">
|
||||
<span>
|
||||
<span v-if="showIcon">
|
||||
<span v-if="showIcon && option.id !== null && option.id !== undefined">
|
||||
<resource-icon v-if="option.icon && option.icon.base64image" :image="option.icon.base64image" size="1x" style="margin-right: 5px"/>
|
||||
<render-icon v-else :icon="defaultIcon" style="margin-right: 5px" />
|
||||
</span>
|
||||
|
|
@ -124,6 +127,10 @@ export default {
|
|||
type: Object,
|
||||
default: null
|
||||
},
|
||||
allowClear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
|
|
@ -135,6 +142,10 @@ export default {
|
|||
pageSize: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
selectFirstOption: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
|
@ -147,7 +158,8 @@ export default {
|
|||
searchTimer: null,
|
||||
scrollHandlerAttached: false,
|
||||
preselectedOptionValue: null,
|
||||
successiveFetches: 0
|
||||
successiveFetches: 0,
|
||||
hasAutoSelectedFirst: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
|
@ -166,6 +178,36 @@ export default {
|
|||
},
|
||||
formattedSearchFooterMessage () {
|
||||
return `${this.$t('label.showing.results.for').replace('%x', this.searchQuery)}`
|
||||
},
|
||||
selectableOptions () {
|
||||
const currentValue = this.$attrs.value
|
||||
// Only filter out null/empty options when the current value is also null/undefined/empty
|
||||
// This prevents such options from being selected and allows the placeholder to show instead
|
||||
if (currentValue === null || currentValue === undefined || currentValue === '') {
|
||||
return this.options.filter(option => {
|
||||
const optionValue = option[this.optionValueKey]
|
||||
return optionValue !== null && optionValue !== undefined && optionValue !== ''
|
||||
})
|
||||
}
|
||||
// When a valid value is selected, show all options
|
||||
return this.options
|
||||
},
|
||||
apiOptionsCount () {
|
||||
if (this.defaultOption) {
|
||||
const defaultOptionValue = this.defaultOption[this.optionValueKey]
|
||||
return this.options.filter(option => option[this.optionValueKey] !== defaultOptionValue).length
|
||||
}
|
||||
return this.options.length
|
||||
},
|
||||
preselectedMatchValue () {
|
||||
// Extract the first value from preselectedOptionValue if it's an array, otherwise return the value itself
|
||||
if (!this.preselectedOptionValue) return null
|
||||
return Array.isArray(this.preselectedOptionValue) ? this.preselectedOptionValue[0] : this.preselectedOptionValue
|
||||
},
|
||||
preselectedMatch () {
|
||||
// Find the matching option for the preselected value
|
||||
if (!this.preselectedMatchValue) return null
|
||||
return this.options.find(entry => entry[this.optionValueKey] === this.preselectedMatchValue) || null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -210,6 +252,7 @@ export default {
|
|||
}).finally(() => {
|
||||
if (this.successiveFetches === 0) {
|
||||
this.loading = false
|
||||
this.autoSelectFirstOptionIfNeeded()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
|
@ -220,11 +263,10 @@ export default {
|
|||
this.resetPreselectedOptionValue()
|
||||
return
|
||||
}
|
||||
const matchValue = Array.isArray(this.preselectedOptionValue) ? this.preselectedOptionValue[0] : this.preselectedOptionValue
|
||||
const match = this.options.find(entry => entry[this.optionValueKey] === matchValue)
|
||||
if (!match) {
|
||||
if (!this.preselectedMatch) {
|
||||
this.successiveFetches++
|
||||
if (this.options.length < this.totalCount) {
|
||||
// Exclude defaultOption from count when comparing with totalCount
|
||||
if (this.apiOptionsCount < this.totalCount) {
|
||||
this.fetchItems()
|
||||
} else {
|
||||
this.resetPreselectedOptionValue()
|
||||
|
|
@ -246,6 +288,36 @@ export default {
|
|||
this.preselectedOptionValue = null
|
||||
this.successiveFetches = 0
|
||||
},
|
||||
autoSelectFirstOptionIfNeeded () {
|
||||
if (!this.selectFirstOption || this.hasAutoSelectedFirst) {
|
||||
return
|
||||
}
|
||||
// Don't auto-select if there's a preselected value being fetched
|
||||
if (this.preselectedOptionValue) {
|
||||
return
|
||||
}
|
||||
const currentValue = this.$attrs.value
|
||||
if (currentValue !== undefined && currentValue !== null && currentValue !== '') {
|
||||
return
|
||||
}
|
||||
if (this.options.length === 0) {
|
||||
return
|
||||
}
|
||||
if (this.searchQuery && this.searchQuery.length > 0) {
|
||||
return
|
||||
}
|
||||
// Only auto-select after initial load is complete (no more successive fetches)
|
||||
if (this.successiveFetches > 0) {
|
||||
return
|
||||
}
|
||||
const firstOption = this.options[0]
|
||||
if (firstOption) {
|
||||
const firstValue = firstOption[this.optionValueKey]
|
||||
this.hasAutoSelectedFirst = true
|
||||
this.$emit('change-option-value', firstValue)
|
||||
this.$emit('change-option', firstOption)
|
||||
}
|
||||
},
|
||||
onSearchTimed (value) {
|
||||
clearTimeout(this.searchTimer)
|
||||
this.searchTimer = setTimeout(() => {
|
||||
|
|
@ -264,7 +336,8 @@ export default {
|
|||
},
|
||||
onScroll (e) {
|
||||
const nearBottom = e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - 10
|
||||
const hasMore = this.options.length < this.totalCount
|
||||
// Exclude defaultOption from count when comparing with totalCount
|
||||
const hasMore = this.apiOptionsCount < this.totalCount
|
||||
if (nearBottom && hasMore && !this.loading) {
|
||||
this.fetchItems()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -356,7 +356,10 @@ export default {
|
|||
permission: ['listVnfAppliances'],
|
||||
resourceType: 'UserVm',
|
||||
params: () => {
|
||||
return { details: 'servoff,tmpl,nics', isvnf: true }
|
||||
return {
|
||||
details: 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff,vnfnics',
|
||||
isvnf: true
|
||||
}
|
||||
},
|
||||
columns: () => {
|
||||
const fields = ['name', 'state', 'ipaddress']
|
||||
|
|
|
|||
|
|
@ -1305,7 +1305,7 @@ export default {
|
|||
for (const deviceId of managementDeviceIds) {
|
||||
if (this.vnfNicNetworks && this.vnfNicNetworks[deviceId] &&
|
||||
((this.vnfNicNetworks[deviceId].type === 'Isolated' && this.vnfNicNetworks[deviceId].vpcid === undefined) ||
|
||||
(this.vnfNicNetworks[deviceId].type === 'Shared' && this.zone.securitygroupsenabled))) {
|
||||
(this.vnfNicNetworks[deviceId].type === 'Shared' && this.vnfNicNetworks[deviceId].service.filter(svc => svc.name === 'SecurityGroupProvider')))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,45 +90,31 @@
|
|||
<template #label>
|
||||
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
:loading="domainLoading"
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.domainid"
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
:selectFirstOption="true"
|
||||
:placeholder="apiParams.domainid.description"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option v-for="domain in domainsList" :key="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>
|
||||
@change-option-value="handleDomainChange" />
|
||||
</a-form-item>
|
||||
<a-form-item name="account" ref="account" v-if="!account">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.account"
|
||||
:loading="loadingAccount"
|
||||
:placeholder="apiParams.account.description"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option v-for="(item, idx) in accountList" :key="idx" :label="item.name">
|
||||
<span>
|
||||
<resource-icon v-if="item && item.icon" :image="item.icon.base64image" size="1x" style="margin-right: 5px"/>
|
||||
<team-outlined v-else style="margin-right: 5px" />
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="name"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
:placeholder="apiParams.account.description" />
|
||||
</a-form-item>
|
||||
<a-form-item name="timezone" ref="timezone">
|
||||
<template #label>
|
||||
|
|
@ -185,12 +171,14 @@ import { timeZone } from '@/utils/timezone'
|
|||
import debounce from 'lodash/debounce'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'AddUser',
|
||||
components: {
|
||||
TooltipLabel,
|
||||
ResourceIcon
|
||||
ResourceIcon,
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
data () {
|
||||
this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
|
||||
|
|
@ -198,14 +186,9 @@ export default {
|
|||
loading: false,
|
||||
timeZoneLoading: false,
|
||||
timeZoneMap: [],
|
||||
domainLoading: false,
|
||||
domainsList: [],
|
||||
selectedDomain: '',
|
||||
samlEnable: false,
|
||||
idpLoading: false,
|
||||
idps: [],
|
||||
loadingAccount: false,
|
||||
accountList: [],
|
||||
account: null,
|
||||
domainid: null
|
||||
}
|
||||
|
|
@ -218,6 +201,19 @@ export default {
|
|||
computed: {
|
||||
samlAllowed () {
|
||||
return 'authorizeSamlSso' in this.$store.getters.apis
|
||||
},
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listall: true,
|
||||
showicon: true,
|
||||
details: 'min'
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
return {
|
||||
showicon: true,
|
||||
domainid: this.form?.domainid || null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -241,53 +237,18 @@ export default {
|
|||
fetchData () {
|
||||
this.account = this.$route.query && this.$route.query.account ? this.$route.query.account : null
|
||||
this.domainid = this.$route.query && this.$route.query.domainid ? this.$route.query.domainid : null
|
||||
if (!this.domianid) {
|
||||
this.fetchDomains()
|
||||
// Set initial domain if provided from route
|
||||
if (this.domainid) {
|
||||
this.form.domainid = this.domainid
|
||||
}
|
||||
this.fetchTimeZone()
|
||||
if (this.samlAllowed) {
|
||||
this.fetchIdps()
|
||||
}
|
||||
},
|
||||
fetchDomains () {
|
||||
this.domainLoading = true
|
||||
var params = {
|
||||
listAll: true,
|
||||
showicon: true,
|
||||
details: 'min'
|
||||
}
|
||||
api('listDomains', params).then(response => {
|
||||
this.domainsList = response.listdomainsresponse.domain || []
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `${this.$t('label.error')} ${error.response.status}`,
|
||||
description: error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
const domainid = this.domainsList[0]?.id || ''
|
||||
this.form.domainid = domainid
|
||||
this.fetchAccount(domainid)
|
||||
this.domainLoading = false
|
||||
})
|
||||
},
|
||||
fetchAccount (domainid) {
|
||||
this.accountList = []
|
||||
handleDomainChange (domainId) {
|
||||
this.form.domainid = domainId
|
||||
this.form.account = null
|
||||
this.loadingAccount = true
|
||||
var params = { listAll: true, showicon: true }
|
||||
if (domainid) {
|
||||
params.domainid = domainid
|
||||
}
|
||||
api('listAccounts', params).then(response => {
|
||||
this.accountList = response.listaccountsresponse.account || []
|
||||
}).catch(error => {
|
||||
this.$notification.error({
|
||||
message: `${this.$t('label.error')} ${error.response.status}`,
|
||||
description: error.response.data.errorresponse.errortext
|
||||
})
|
||||
}).finally(() => {
|
||||
this.loadingAccount = false
|
||||
})
|
||||
},
|
||||
fetchTimeZone (value) {
|
||||
this.timeZoneMap = []
|
||||
|
|
@ -328,12 +289,14 @@ export default {
|
|||
accounttype: 0
|
||||
}
|
||||
|
||||
// Account: use route query account if available, otherwise use form value (which is the account name)
|
||||
if (this.account) {
|
||||
params.account = this.account
|
||||
} else if (this.accountList[values.account]) {
|
||||
params.account = this.accountList[values.account].name
|
||||
} else if (values.account) {
|
||||
params.account = values.account
|
||||
}
|
||||
|
||||
// Domain: use route query domainid if available, otherwise use form value
|
||||
if (this.domainid) {
|
||||
params.domainid = this.domainid
|
||||
} else if (values.domainid) {
|
||||
|
|
|
|||
|
|
@ -121,15 +121,20 @@
|
|||
ref="domain"
|
||||
name="domain"
|
||||
>
|
||||
<a-auto-complete
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.domain"
|
||||
:options="domains"
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
:placeholder="$t('label.domain')"
|
||||
:filter-option="filterOption"
|
||||
:defaultOption="{ id: null, path: ''}"
|
||||
:allowClear="true"
|
||||
style="width: 100%;"
|
||||
@select="getAccounts"
|
||||
:dropdownMatchSelectWidth="false"
|
||||
/>
|
||||
@change-option-value="handleDomainChange"
|
||||
@change-option="handleDomainOptionChange" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
|
@ -150,15 +155,20 @@
|
|||
ref="account"
|
||||
name="account"
|
||||
>
|
||||
<a-auto-complete
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.account"
|
||||
:options="accounts"
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
:placeholder="$t('label.account')"
|
||||
:filter-option="filterOption"
|
||||
:disabled="form.isRecursive"
|
||||
:dropdownMatchSelectWidth="false"
|
||||
@select="selectAccount"
|
||||
/>
|
||||
:defaultOption="{ id: null, name: ''}"
|
||||
allowClear="true"
|
||||
@change-option-value="selectAccount"
|
||||
@change-option="selectAccountOption" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="3" v-if="'listUsageTypes' in $store.getters.apis">
|
||||
|
|
@ -361,6 +371,7 @@ import ListView from '@/components/view/ListView'
|
|||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import TooltipButton from '@/components/widgets/TooltipButton'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.extend(utc)
|
||||
|
|
@ -374,7 +385,8 @@ export default {
|
|||
ListView,
|
||||
Status,
|
||||
TooltipLabel,
|
||||
TooltipButton
|
||||
TooltipButton,
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
@ -402,8 +414,6 @@ export default {
|
|||
page: 1,
|
||||
pageSize: 20,
|
||||
usageTypes: [],
|
||||
domains: [],
|
||||
accounts: [],
|
||||
account: null,
|
||||
domain: null,
|
||||
usageType: null,
|
||||
|
|
@ -436,6 +446,23 @@ export default {
|
|||
this.fetchData()
|
||||
this.updateColumns()
|
||||
},
|
||||
computed: {
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listall: true
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
if (!this.form.domain) {
|
||||
return {
|
||||
listall: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
domainid: this.form.domain
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearFilters () {
|
||||
this.formRef.value.resetFields()
|
||||
|
|
@ -445,8 +472,6 @@ export default {
|
|||
this.usageType = null
|
||||
this.page = 1
|
||||
this.pageSize = 20
|
||||
|
||||
this.getAccounts()
|
||||
},
|
||||
disabledDate (current) {
|
||||
return current && current > dayjs().endOf('day')
|
||||
|
|
@ -473,8 +498,6 @@ export default {
|
|||
this.listUsageServerMetrics()
|
||||
this.getUsageTypes()
|
||||
this.getAllUsageRecordColumns()
|
||||
this.getDomains()
|
||||
this.getAccounts()
|
||||
if (!this.$store.getters.customColumns[this.$store.getters.userInfo.id]) {
|
||||
this.$store.getters.customColumns[this.$store.getters.userInfo.id] = {}
|
||||
this.$store.getters.customColumns[this.$store.getters.userInfo.id][this.$route.path] = this.selectedColumnKeys
|
||||
|
|
@ -528,16 +551,6 @@ export default {
|
|||
this.formRef.value.scrollToField(error.errorFields[0].name)
|
||||
})
|
||||
},
|
||||
selectAccount (value, option) {
|
||||
if (option && option.id) {
|
||||
this.account = option
|
||||
} else {
|
||||
this.account = null
|
||||
if (this.formRef?.value) {
|
||||
this.formRef.value.resetFields('account')
|
||||
}
|
||||
}
|
||||
},
|
||||
selectUsageType (value, option) {
|
||||
if (option && option.id) {
|
||||
this.usageType = option
|
||||
|
|
@ -548,24 +561,12 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
getDomains () {
|
||||
api('listDomains', { listAll: true }).then(json => {
|
||||
if (json && json.listdomainsresponse && json.listdomainsresponse.domain) {
|
||||
this.domains = [{ id: null, value: '' }, ...json.listdomainsresponse.domain.map(x => {
|
||||
return {
|
||||
id: x.id,
|
||||
value: x.path
|
||||
}
|
||||
})]
|
||||
}
|
||||
})
|
||||
handleDomainChange (domainId) {
|
||||
this.form.domain = domainId
|
||||
this.form.account = null
|
||||
},
|
||||
getAccounts (value, option) {
|
||||
var params = {
|
||||
listAll: true
|
||||
}
|
||||
handleDomainOptionChange (option) {
|
||||
if (option && option.id) {
|
||||
params.domainid = option.id
|
||||
this.domain = option
|
||||
} else {
|
||||
this.domain = null
|
||||
|
|
@ -573,16 +574,19 @@ export default {
|
|||
this.formRef.value.resetFields('domain')
|
||||
}
|
||||
}
|
||||
api('listAccounts', params).then(json => {
|
||||
if (json && json.listaccountsresponse && json.listaccountsresponse.account) {
|
||||
this.accounts = [{ id: null, value: '' }, ...json.listaccountsresponse.account.map(x => {
|
||||
return {
|
||||
id: x.id,
|
||||
value: x.name
|
||||
}
|
||||
})]
|
||||
},
|
||||
selectAccount (accountId) {
|
||||
this.form.account = accountId
|
||||
},
|
||||
selectAccountOption (option) {
|
||||
if (option && option.id) {
|
||||
this.account = option
|
||||
} else {
|
||||
this.account = null
|
||||
if (this.formRef?.value) {
|
||||
this.formRef.value.resetFields('account')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
getParams (page, pageSize) {
|
||||
const formRaw = toRaw(this.form)
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export default {
|
|||
methods: {
|
||||
fetchData () {
|
||||
var params = {
|
||||
details: 'servoff,tmpl,nics',
|
||||
details: 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp,backoff,vnfnics',
|
||||
isVnf: true,
|
||||
listAll: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,42 +73,32 @@
|
|||
<template #label>
|
||||
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.domainid"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
:loading="domainLoading"
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
allowClear="true"
|
||||
:placeholder="apiParams.domainid.description"
|
||||
@change="val => { handleDomainChange(val) }">
|
||||
<a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex" :label="opt.path || opt.name || opt.description" :value="opt.id">
|
||||
<span>
|
||||
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
|
||||
<block-outlined v-else style="margin-right: 5px" />
|
||||
{{ opt.path || opt.name || opt.description }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
@change-option-value="handleDomainChange" />
|
||||
</a-form-item>
|
||||
<a-form-item name="account" ref="account" v-if="domainid">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.account"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
:placeholder="apiParams.account.description"
|
||||
@change="val => { handleAccountChange(val) }">
|
||||
<a-select-option v-for="(acc, index) in accounts" :value="acc.name" :key="index">
|
||||
{{ acc.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="name"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
allowClear="true"
|
||||
:placeholder="apiParams.account.description" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
|
|
@ -199,13 +189,15 @@ import { api } from '@/api'
|
|||
import { mixinForm } from '@/utils/mixin'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'CreateTemplate',
|
||||
mixins: [mixinForm],
|
||||
components: {
|
||||
ResourceIcon,
|
||||
TooltipLabel
|
||||
TooltipLabel,
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
@ -219,9 +211,6 @@ export default {
|
|||
zones: [],
|
||||
osTypes: {},
|
||||
loading: false,
|
||||
domains: [],
|
||||
accounts: [],
|
||||
domainLoading: false,
|
||||
domainid: null,
|
||||
account: null,
|
||||
architectureTypes: {}
|
||||
|
|
@ -230,6 +219,21 @@ export default {
|
|||
computed: {
|
||||
isAdminRole () {
|
||||
return this.$store.getters.userInfo.roletype === 'Admin'
|
||||
},
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listall: true,
|
||||
showicon: true,
|
||||
details: 'min'
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
if (!this.domainid) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
domainid: this.domainid
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
|
|
@ -256,9 +260,6 @@ export default {
|
|||
if (this.resource.intervaltype) {
|
||||
this.fetchSnapshotZones()
|
||||
}
|
||||
if ('listDomains' in this.$store.getters.apis) {
|
||||
this.fetchDomains()
|
||||
}
|
||||
this.architectureTypes.opts = this.$fetchCpuArchitectureTypes()
|
||||
},
|
||||
fetchOsTypes () {
|
||||
|
|
@ -309,44 +310,16 @@ export default {
|
|||
}
|
||||
})
|
||||
},
|
||||
fetchDomains () {
|
||||
const params = {}
|
||||
params.listAll = true
|
||||
params.showicon = true
|
||||
params.details = 'min'
|
||||
this.domainLoading = true
|
||||
api('listDomains', params).then(json => {
|
||||
this.domains = json.listdomainsresponse.domain
|
||||
}).finally(() => {
|
||||
this.domainLoading = false
|
||||
this.handleDomainChange(null)
|
||||
})
|
||||
},
|
||||
async handleDomainChange (domain) {
|
||||
this.domainid = domain
|
||||
handleDomainChange (domainId) {
|
||||
this.domainid = domainId
|
||||
this.form.account = null
|
||||
this.account = null
|
||||
if ('listAccounts' in this.$store.getters.apis) {
|
||||
await this.fetchAccounts()
|
||||
}
|
||||
},
|
||||
fetchAccounts () {
|
||||
return new Promise((resolve, reject) => {
|
||||
api('listAccounts', {
|
||||
domainid: this.domainid
|
||||
}).then(response => {
|
||||
this.accounts = response?.listaccountsresponse?.account || []
|
||||
resolve(this.accounts)
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
handleAccountChange (acc) {
|
||||
if (acc) {
|
||||
this.account = acc.name
|
||||
handleAccountChange (accountName) {
|
||||
if (accountName) {
|
||||
this.account = accountName
|
||||
} else {
|
||||
this.account = acc
|
||||
this.account = null
|
||||
}
|
||||
},
|
||||
handleSubmit (e) {
|
||||
|
|
|
|||
|
|
@ -57,43 +57,33 @@
|
|||
<template #label>
|
||||
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.zoneId"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option :value="zone.id" v-for="zone in zones" :key="zone.id" :label="zone.name || zone.description">
|
||||
<span>
|
||||
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
|
||||
<global-outlined v-else style="margin-right: 5px"/>
|
||||
{{ zone.name || zone.description }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
api="listZones"
|
||||
:apiParams="zonesApiParams"
|
||||
resourceType="zone"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="global-outlined"
|
||||
selectFirstOption="true"
|
||||
@change-option-value="handleZoneChange" />
|
||||
</a-form-item>
|
||||
<a-form-item name="diskofferingid" ref="diskofferingid">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.diskofferingid"
|
||||
:loading="offeringLoading"
|
||||
api="listDiskOfferings"
|
||||
:apiParams="diskOfferingsApiParams"
|
||||
resourceType="diskoffering"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="displaytext"
|
||||
defaultIcon="hdd-outlined"
|
||||
:defaultOption="{ id: null, displaytext: ''}"
|
||||
allowClear="true"
|
||||
:placeholder="apiParams.diskofferingid.description"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option
|
||||
v-for="(offering, index) in offerings"
|
||||
:value="offering.id"
|
||||
:key="index"
|
||||
:label="offering.displaytext || offering.name">
|
||||
{{ offering.displaytext || offering.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
@change-option="onChangeDiskOffering" />
|
||||
</a-form-item>
|
||||
<a-form-item ref="format" name="format">
|
||||
<template #label>
|
||||
|
|
@ -124,38 +114,33 @@
|
|||
<template #label>
|
||||
<tooltip-label :title="$t('label.domain')" :tooltip="apiParams.domainid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.domainid"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
:loading="domainLoading"
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
:placeholder="$t('label.domainid')"
|
||||
@change="val => { handleDomainChange(domainList[val].id) }">
|
||||
<a-select-option v-for="(opt, optIndex) in domainList" :key="optIndex" :label="opt.path || opt.name || opt.description">
|
||||
{{ opt.path || opt.name || opt.description }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
allowClear="true"
|
||||
@change-option-value="handleDomainChange" />
|
||||
</a-form-item>
|
||||
<a-form-item name="account" ref="account" v-if="'listDomains' in $store.getters.apis">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.account"
|
||||
showSearch
|
||||
optionFilterProp="value"
|
||||
:filterOption="(input, option) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="name"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
allowClear="true"
|
||||
:placeholder="$t('label.account')"
|
||||
@change="val => { handleAccountChange(val) }">
|
||||
<a-select-option v-for="(acc, index) in accountList" :value="acc.name" :key="index">
|
||||
{{ acc.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
@change-option-value="handleAccountChange" />
|
||||
</a-form-item>
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||
|
|
@ -173,27 +158,25 @@ import { axios } from '../../utils/request'
|
|||
import { mixinForm } from '@/utils/mixin'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'UploadLocalVolume',
|
||||
mixins: [mixinForm],
|
||||
components: {
|
||||
ResourceIcon,
|
||||
TooltipLabel
|
||||
TooltipLabel,
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
fileList: [],
|
||||
zones: [],
|
||||
domainList: [],
|
||||
accountList: [],
|
||||
offerings: [],
|
||||
offeringLoading: false,
|
||||
formats: ['RAW', 'VHD', 'VHDX', 'OVA', 'QCOW2'],
|
||||
domainId: null,
|
||||
account: null,
|
||||
uploadParams: null,
|
||||
domainLoading: false,
|
||||
customDiskOffering: false,
|
||||
isCustomizedDiskIOps: false,
|
||||
loading: false,
|
||||
uploadPercentage: 0
|
||||
}
|
||||
|
|
@ -201,9 +184,38 @@ export default {
|
|||
beforeCreate () {
|
||||
this.apiParams = this.$getApiParams('getUploadParamsForVolume')
|
||||
},
|
||||
computed: {
|
||||
zonesApiParams () {
|
||||
return {
|
||||
showicon: true
|
||||
}
|
||||
},
|
||||
diskOfferingsApiParams () {
|
||||
if (!this.form.zoneId) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
zoneid: this.form.zoneId,
|
||||
listall: true
|
||||
}
|
||||
},
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listall: true,
|
||||
details: 'min'
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
if (!this.form.domainid) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
domainid: this.form.domainid
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initForm()
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
initForm () {
|
||||
|
|
@ -221,38 +233,18 @@ export default {
|
|||
zoneId: [{ required: true, message: this.$t('message.error.select') }]
|
||||
})
|
||||
},
|
||||
listZones () {
|
||||
api('listZones', { showicon: true }).then(json => {
|
||||
if (json && json.listzonesresponse && json.listzonesresponse.zone) {
|
||||
this.zones = json.listzonesresponse.zone
|
||||
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
|
||||
if (this.zones.length > 0) {
|
||||
this.onZoneChange(this.zones[0].id)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onZoneChange (zoneId) {
|
||||
handleZoneChange (zoneId) {
|
||||
this.form.zoneId = zoneId
|
||||
this.zoneId = zoneId
|
||||
this.fetchDiskOfferings(zoneId)
|
||||
// InfiniteScrollSelect will auto-reload disk offerings when apiParams changes
|
||||
},
|
||||
fetchDiskOfferings (zoneId) {
|
||||
this.offeringLoading = true
|
||||
this.offerings = [{ id: -1, name: '' }]
|
||||
this.form.diskofferingid = undefined
|
||||
api('listDiskOfferings', {
|
||||
zoneid: zoneId,
|
||||
listall: true
|
||||
}).then(json => {
|
||||
for (var offering of json.listdiskofferingsresponse.diskoffering) {
|
||||
if (offering.iscustomized) {
|
||||
this.offerings.push(offering)
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
this.offeringLoading = false
|
||||
})
|
||||
onChangeDiskOffering (offering) {
|
||||
if (offering) {
|
||||
this.customDiskOffering = offering.iscustomized || false
|
||||
this.isCustomizedDiskIOps = offering.iscustomizediops || false
|
||||
} else {
|
||||
this.customDiskOffering = false
|
||||
this.isCustomizedDiskIOps = false
|
||||
}
|
||||
},
|
||||
handleRemove (file) {
|
||||
const index = this.fileList.indexOf(file)
|
||||
|
|
@ -266,53 +258,14 @@ export default {
|
|||
this.form.file = file
|
||||
return false
|
||||
},
|
||||
handleDomainChange (domain) {
|
||||
this.domainId = domain
|
||||
if ('listAccounts' in this.$store.getters.apis) {
|
||||
this.fetchAccounts()
|
||||
}
|
||||
handleDomainChange (domainId) {
|
||||
this.form.domainid = domainId
|
||||
this.domainId = domainId
|
||||
this.form.account = null
|
||||
},
|
||||
handleAccountChange (acc) {
|
||||
if (acc) {
|
||||
this.account = acc.name
|
||||
} else {
|
||||
this.account = acc
|
||||
}
|
||||
},
|
||||
fetchData () {
|
||||
this.listZones()
|
||||
if ('listDomains' in this.$store.getters.apis) {
|
||||
this.fetchDomains()
|
||||
}
|
||||
},
|
||||
fetchDomains () {
|
||||
this.domainLoading = true
|
||||
api('listDomains', {
|
||||
listAll: true,
|
||||
details: 'min'
|
||||
}).then(response => {
|
||||
this.domainList = response.listdomainsresponse.domain
|
||||
|
||||
if (this.domainList[0]) {
|
||||
this.handleDomainChange(null)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.domainLoading = false
|
||||
})
|
||||
},
|
||||
fetchAccounts () {
|
||||
api('listAccounts', {
|
||||
domainid: this.domainId
|
||||
}).then(response => {
|
||||
this.accountList = response.listaccountsresponse.account || []
|
||||
if (this.accountList && this.accountList.length === 0) {
|
||||
this.handleAccountChange(null)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
})
|
||||
handleAccountChange (accountName) {
|
||||
this.form.account = accountName
|
||||
this.account = accountName
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
|
|
|
|||
|
|
@ -47,21 +47,16 @@
|
|||
<template #label>
|
||||
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.zoneId"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option :value="zone.id" v-for="zone in zones" :key="zone.id" :label="zone.name || zone.description">
|
||||
<span>
|
||||
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
|
||||
<global-outlined v-else style="margin-right: 5px"/>
|
||||
{{ zone.name || zone.description }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
api="listZones"
|
||||
:apiParams="zonesApiParams"
|
||||
resourceType="zone"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="global-outlined"
|
||||
selectFirstOption="true"
|
||||
@change-option-value="handleZoneChange" />
|
||||
</a-form-item>
|
||||
<a-form-item name="format" ref="format">
|
||||
<template #label>
|
||||
|
|
@ -83,23 +78,17 @@
|
|||
<template #label>
|
||||
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description || $t('label.diskoffering')"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.diskofferingid"
|
||||
:loading="loading"
|
||||
@change="id => onChangeDiskOffering(id)"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option
|
||||
v-for="(offering, index) in offerings"
|
||||
:value="offering.id"
|
||||
:key="index"
|
||||
:label="offering.displaytext || offering.name">
|
||||
{{ offering.displaytext || offering.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
api="listDiskOfferings"
|
||||
:apiParams="diskOfferingsApiParams"
|
||||
resourceType="diskoffering"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="displaytext"
|
||||
defaultIcon="hdd-outlined"
|
||||
:defaultOption="{ id: null, displaytext: ''}"
|
||||
allowClear="true"
|
||||
@change-option="onChangeDiskOffering" />
|
||||
</a-form-item>
|
||||
<a-form-item name="checksum" ref="checksum">
|
||||
<template #label>
|
||||
|
|
@ -114,38 +103,33 @@
|
|||
<template #label>
|
||||
<tooltip-label :title="$t('label.domain')" :tooltip="apiParams.domainid.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.domainid"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
:loading="domainLoading"
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
allowClear="true"
|
||||
:placeholder="$t('label.domainid')"
|
||||
@change="val => { handleDomainChange(domainList[val].id) }">
|
||||
<a-select-option v-for="(opt, optIndex) in domainList" :key="optIndex" :label="opt.path || opt.name || opt.description">
|
||||
{{ opt.path || opt.name || opt.description }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
@change-option-value="handleDomainChange" />
|
||||
</a-form-item>
|
||||
<a-form-item name="account" ref="account" v-if="'listDomains' in $store.getters.apis">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.account"
|
||||
showSearch
|
||||
optionFilterProp="value"
|
||||
:filterOption="(input, option) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="name"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
:placeholder="$t('label.account')"
|
||||
@change="val => { handleAccountChange(val) }">
|
||||
<a-select-option v-for="(acc, index) in accountList" :value="acc.name" :key="index">
|
||||
{{ acc.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
allowClear="true"
|
||||
@change-option-value="handleAccountChange" />
|
||||
</a-form-item>
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||
|
|
@ -162,27 +146,26 @@ import { api } from '@/api'
|
|||
import { mixinForm } from '@/utils/mixin'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'UploadVolume',
|
||||
mixins: [mixinForm],
|
||||
components: {
|
||||
ResourceIcon,
|
||||
TooltipLabel
|
||||
TooltipLabel,
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
zones: [],
|
||||
domainList: [],
|
||||
accountList: [],
|
||||
formats: ['RAW', 'VHD', 'VHDX', 'OVA', 'QCOW2'],
|
||||
offerings: [],
|
||||
zoneSelected: '',
|
||||
selectedDiskOfferingId: null,
|
||||
domainId: null,
|
||||
account: null,
|
||||
uploadParams: null,
|
||||
domainLoading: false,
|
||||
customDiskOffering: false,
|
||||
isCustomizedDiskIOps: false,
|
||||
loading: false,
|
||||
uploadPercentage: 0
|
||||
}
|
||||
|
|
@ -190,9 +173,36 @@ export default {
|
|||
beforeCreate () {
|
||||
this.apiParams = this.$getApiParams('uploadVolume')
|
||||
},
|
||||
computed: {
|
||||
zonesApiParams () {
|
||||
return {
|
||||
showicon: true
|
||||
}
|
||||
},
|
||||
diskOfferingsApiParams () {
|
||||
if (!this.form.zoneId) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
zoneid: this.form.zoneId,
|
||||
listall: true
|
||||
}
|
||||
},
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listall: true,
|
||||
details: 'min'
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
return {
|
||||
domainid: this.form?.domainid || null,
|
||||
showicon: true
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initForm()
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
initForm () {
|
||||
|
|
@ -207,78 +217,28 @@ export default {
|
|||
format: [{ required: true, message: this.$t('message.error.select') }]
|
||||
})
|
||||
},
|
||||
fetchData () {
|
||||
this.loading = true
|
||||
api('listZones', { showicon: true }).then(json => {
|
||||
this.zones = json.listzonesresponse.zone || []
|
||||
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
|
||||
this.form.zoneId = this.zones[0].id || ''
|
||||
this.fetchDiskOfferings(this.form.zoneId)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
if ('listDomains' in this.$store.getters.apis) {
|
||||
this.fetchDomains()
|
||||
}
|
||||
handleZoneChange (zoneId) {
|
||||
this.form.zoneId = zoneId
|
||||
// InfiniteScrollSelect will auto-reload disk offerings when apiParams changes
|
||||
},
|
||||
fetchDiskOfferings (zoneId) {
|
||||
this.loading = true
|
||||
api('listDiskOfferings', {
|
||||
zoneid: zoneId,
|
||||
listall: true
|
||||
}).then(json => {
|
||||
this.offerings = json.listdiskofferingsresponse.diskoffering || []
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
fetchDomains () {
|
||||
this.domainLoading = true
|
||||
api('listDomains', {
|
||||
listAll: true,
|
||||
details: 'min'
|
||||
}).then(response => {
|
||||
this.domainList = response.listdomainsresponse.domain
|
||||
|
||||
if (this.domainList[0]) {
|
||||
this.handleDomainChange(null)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.domainLoading = false
|
||||
})
|
||||
},
|
||||
fetchAccounts () {
|
||||
api('listAccounts', {
|
||||
domainid: this.domainId
|
||||
}).then(response => {
|
||||
this.accountList = response.listaccountsresponse.account || []
|
||||
if (this.accountList && this.accountList.length === 0) {
|
||||
this.handleAccountChange(null)
|
||||
}
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
})
|
||||
},
|
||||
onChangeDiskOffering (id) {
|
||||
const offering = this.offerings.filter(x => x.id === id)
|
||||
this.customDiskOffering = offering[0]?.iscustomized || false
|
||||
this.isCustomizedDiskIOps = offering[0]?.iscustomizediops || false
|
||||
},
|
||||
handleDomainChange (domain) {
|
||||
this.domainId = domain
|
||||
if ('listAccounts' in this.$store.getters.apis) {
|
||||
this.fetchAccounts()
|
||||
}
|
||||
},
|
||||
handleAccountChange (acc) {
|
||||
if (acc) {
|
||||
this.account = acc.name
|
||||
onChangeDiskOffering (offering) {
|
||||
if (offering) {
|
||||
this.customDiskOffering = offering.iscustomized || false
|
||||
this.isCustomizedDiskIOps = offering.iscustomizediops || false
|
||||
} else {
|
||||
this.account = acc
|
||||
this.customDiskOffering = false
|
||||
this.isCustomizedDiskIOps = false
|
||||
}
|
||||
},
|
||||
handleDomainChange (domainId) {
|
||||
this.form.domainid = domainId
|
||||
this.domainId = domainId
|
||||
this.form.account = null
|
||||
},
|
||||
handleAccountChange (accountName) {
|
||||
this.form.account = accountName
|
||||
this.account = accountName
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
|
|
|
|||
|
|
@ -67,39 +67,33 @@
|
|||
<info-circle-outlined style="color: rgba(0,0,0,.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
id="domain-selection"
|
||||
v-model:value="form.domainid"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
:loading="domainLoading"
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
:defaultOption="{ id: null, path: ''}"
|
||||
allowClear="true"
|
||||
:placeholder="apiParams.domainid.description"
|
||||
@change="val => { handleDomainChanged(val) }">
|
||||
<a-select-option v-for="opt in domains" :key="opt.id" :label="opt.path || opt.name || opt.description || ''">
|
||||
{{ opt.path || opt.name || opt.description }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
@change-option-value="handleDomainChanged" />
|
||||
</a-form-item>
|
||||
<a-form-item name="account" ref="account" v-if="isAdminOrDomainAdmin && ['Local'].includes(form.scope) && form.domainid">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
<infinite-scroll-select
|
||||
v-model:value="form.account"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
:filterOption="(input, option) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}"
|
||||
:loading="accountLoading"
|
||||
:placeholder="apiParams.account.description">
|
||||
<a-select-option v-for="opt in accounts" :key="opt.id" :label="opt.name">
|
||||
{{ opt.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="name"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
:placeholder="apiParams.account.description" />
|
||||
</a-form-item>
|
||||
<a-form-item name="payloadurl" ref="payloadurl">
|
||||
<template #label>
|
||||
|
|
@ -156,25 +150,22 @@
|
|||
<script>
|
||||
import { ref, reactive, toRaw } from 'vue'
|
||||
import { api } from '@/api'
|
||||
import _ from 'lodash'
|
||||
import { mixinForm } from '@/utils/mixin'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import TestWebhookDeliveryView from '@/components/view/TestWebhookDeliveryView'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
export default {
|
||||
name: 'CreateWebhook',
|
||||
mixins: [mixinForm],
|
||||
components: {
|
||||
TooltipLabel,
|
||||
TestWebhookDeliveryView
|
||||
TestWebhookDeliveryView,
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
props: {},
|
||||
data () {
|
||||
return {
|
||||
domains: [],
|
||||
domainLoading: false,
|
||||
accounts: [],
|
||||
accountLoading: false,
|
||||
loading: false,
|
||||
testDeliveryAllowed: false,
|
||||
testDeliveryLoading: false
|
||||
|
|
@ -185,9 +176,6 @@ export default {
|
|||
},
|
||||
created () {
|
||||
this.initForm()
|
||||
if (['Domain', 'Local'].includes(this.form.scope)) {
|
||||
this.fetchDomainData()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isAdminOrDomainAdmin () {
|
||||
|
|
@ -201,6 +189,21 @@ export default {
|
|||
return this.form.payloadurl.toLowerCase().startsWith('https://')
|
||||
}
|
||||
return false
|
||||
},
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listAll: true,
|
||||
showicon: true,
|
||||
details: 'min'
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
if (!this.form.domainid) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
domainid: this.form.domainid
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -228,46 +231,6 @@ export default {
|
|||
updateTestDeliveryLoading (value) {
|
||||
this.testDeliveryLoading = value
|
||||
},
|
||||
fetchDomainData () {
|
||||
this.domainLoading = true
|
||||
this.domains = [
|
||||
{
|
||||
id: null,
|
||||
name: ''
|
||||
}
|
||||
]
|
||||
this.form.domainid = null
|
||||
this.form.account = null
|
||||
api('listDomains', {}).then(json => {
|
||||
const listdomains = json.listdomainsresponse.domain
|
||||
this.domains = this.domains.concat(listdomains)
|
||||
}).finally(() => {
|
||||
this.domainLoading = false
|
||||
if (this.arrayHasItems(this.domains)) {
|
||||
this.form.domainid = null
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchAccountData () {
|
||||
this.accounts = []
|
||||
this.form.account = null
|
||||
if (!this.form.domainid) {
|
||||
return
|
||||
}
|
||||
this.accountLoading = true
|
||||
var params = {
|
||||
domainid: this.form.domainid
|
||||
}
|
||||
api('listAccounts', params).then(json => {
|
||||
const listAccounts = json.listaccountsresponse.account || []
|
||||
this.accounts = listAccounts
|
||||
}).finally(() => {
|
||||
this.accountLoading = false
|
||||
if (this.arrayHasItems(this.accounts)) {
|
||||
this.form.account = this.accounts[0].id
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
|
|
@ -300,10 +263,8 @@ export default {
|
|||
return
|
||||
}
|
||||
if (values.account) {
|
||||
const accountItem = _.find(this.accounts, (option) => option.id === values.account)
|
||||
if (accountItem) {
|
||||
params.account = accountItem.name
|
||||
}
|
||||
// values.account is the account name (optionValueKey="name")
|
||||
params.account = values.account
|
||||
}
|
||||
this.loading = true
|
||||
api('createWebhook', params).then(json => {
|
||||
|
|
@ -331,14 +292,11 @@ export default {
|
|||
}, 1)
|
||||
},
|
||||
handleScopeChange (e) {
|
||||
if (['Domain', 'Local'].includes(this.form.scope)) {
|
||||
this.fetchDomainData()
|
||||
}
|
||||
this.form.domainid = null
|
||||
this.form.account = null
|
||||
},
|
||||
handleDomainChanged (domainid) {
|
||||
if (domainid) {
|
||||
this.fetchAccountData()
|
||||
}
|
||||
this.form.account = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -372,22 +372,16 @@
|
|||
name="domain"
|
||||
ref="domain"
|
||||
:label="$t('label.domain')">
|
||||
<a-select
|
||||
@change="changeDomain"
|
||||
<infinite-scroll-select
|
||||
v-model:value="importForm.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>
|
||||
api="listDomains"
|
||||
:apiParams="domainsApiParams"
|
||||
resourceType="domain"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="path"
|
||||
defaultIcon="block-outlined"
|
||||
allowClear="true"
|
||||
@change-option-value="changeDomain" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
|
|
@ -395,22 +389,16 @@
|
|||
name="account"
|
||||
ref="account"
|
||||
:label="$t('label.account')">
|
||||
<a-select
|
||||
@change="changeAccount"
|
||||
<infinite-scroll-select
|
||||
v-model:value="importForm.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>
|
||||
api="listAccounts"
|
||||
:apiParams="accountsApiParams"
|
||||
resourceType="account"
|
||||
optionValueKey="name"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="team-outlined"
|
||||
allowClear="true"
|
||||
@change-option-value="changeAccount" />
|
||||
<span v-if="importForm.accountError" class="required">{{ $t('label.required') }}</span>
|
||||
</a-form-item>
|
||||
|
||||
|
|
@ -419,22 +407,16 @@
|
|||
name="project"
|
||||
ref="project"
|
||||
:label="$t('label.project')">
|
||||
<a-select
|
||||
@change="changeProject"
|
||||
<infinite-scroll-select
|
||||
v-model:value="importForm.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>
|
||||
api="listProjects"
|
||||
:apiParams="projectsApiParams"
|
||||
resourceType="project"
|
||||
optionValueKey="id"
|
||||
optionLabelKey="name"
|
||||
defaultIcon="project-outlined"
|
||||
allowClear="true"
|
||||
@change-option-value="changeProject" />
|
||||
<span v-if="importForm.projectError" class="required">{{ $t('label.required') }}</span>
|
||||
</a-form-item>
|
||||
|
||||
|
|
@ -480,6 +462,7 @@ import Status from '@/components/widgets/Status'
|
|||
import SearchView from '@/components/view/SearchView'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel.vue'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -487,7 +470,8 @@ export default {
|
|||
Breadcrumb,
|
||||
Status,
|
||||
SearchView,
|
||||
ResourceIcon
|
||||
ResourceIcon,
|
||||
InfiniteScrollSelect
|
||||
},
|
||||
name: 'ManageVolumes',
|
||||
data () {
|
||||
|
|
@ -607,7 +591,6 @@ export default {
|
|||
this.page.managed = parseInt(this.$route.query.managedpage || 1)
|
||||
this.initForm()
|
||||
this.fetchData()
|
||||
this.fetchDomains()
|
||||
},
|
||||
computed: {
|
||||
isPageAllowed () {
|
||||
|
|
@ -629,6 +612,36 @@ export default {
|
|||
showCluster () {
|
||||
return this.poolscope !== 'zone'
|
||||
},
|
||||
domainsApiParams () {
|
||||
return {
|
||||
listall: true,
|
||||
details: 'min',
|
||||
showicon: true
|
||||
}
|
||||
},
|
||||
accountsApiParams () {
|
||||
if (!this.importForm.selectedDomain) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
domainid: this.importForm.selectedDomain,
|
||||
showicon: true,
|
||||
state: 'Enabled',
|
||||
isrecursive: false
|
||||
}
|
||||
},
|
||||
projectsApiParams () {
|
||||
if (!this.importForm.selectedDomain) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
domainid: this.importForm.selectedDomain,
|
||||
state: 'Active',
|
||||
showicon: true,
|
||||
details: 'min',
|
||||
isrecursive: false
|
||||
}
|
||||
},
|
||||
showHost () {
|
||||
return this.poolscope === 'host'
|
||||
},
|
||||
|
|
@ -970,53 +983,6 @@ export default {
|
|||
this.updateQuery('scope', value)
|
||||
this.fetchOptions(this.params.zones, 'zones', value)
|
||||
},
|
||||
fetchDomains () {
|
||||
api('listDomains', {
|
||||
response: 'json',
|
||||
listAll: true,
|
||||
showicon: true,
|
||||
details: 'min'
|
||||
}).then(response => {
|
||||
this.domains = response.listdomainsresponse.domain || []
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
fetchAccounts () {
|
||||
this.loading = true
|
||||
api('listAccounts', {
|
||||
response: 'json',
|
||||
domainId: this.importForm.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.importForm.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
|
||||
})
|
||||
},
|
||||
changeAccountType () {
|
||||
this.importForm.selectedDomain = null
|
||||
this.importForm.selectedAccount = null
|
||||
|
|
@ -1029,8 +995,7 @@ export default {
|
|||
this.importForm.selectedProject = null
|
||||
this.importForm.selectedDiskoffering = null
|
||||
this.diskOfferings = {}
|
||||
this.fetchAccounts()
|
||||
this.fetchProjects()
|
||||
// InfiniteScrollSelect will auto-reload when apiParams changes
|
||||
},
|
||||
changeAccount () {
|
||||
this.importForm.selectedProject = null
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
|
@ -708,13 +710,31 @@ public class Script implements Callable<String> {
|
|||
return executeCommandForExitValue(0, command);
|
||||
}
|
||||
|
||||
private static void cleanupProcesses(AtomicReference<List<Process>> processesRef) {
|
||||
List<Process> processes = processesRef.get();
|
||||
if (CollectionUtils.isNotEmpty(processes)) {
|
||||
for (Process process : processes) {
|
||||
if (process == null) {
|
||||
continue;
|
||||
}
|
||||
LOGGER.trace(String.format("Cleaning up process [%s] from piped commands.", process.pid()));
|
||||
IOUtils.closeQuietly(process.getErrorStream());
|
||||
IOUtils.closeQuietly(process.getOutputStream());
|
||||
IOUtils.closeQuietly(process.getInputStream());
|
||||
process.destroyForcibly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Pair<Integer, String> executePipedCommands(List<String[]> commands, long timeout) {
|
||||
if (timeout <= 0) {
|
||||
timeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
final AtomicReference<List<Process>> processesRef = new AtomicReference<>();
|
||||
Callable<Pair<Integer, String>> commandRunner = () -> {
|
||||
List<ProcessBuilder> builders = commands.stream().map(ProcessBuilder::new).collect(Collectors.toList());
|
||||
List<Process> processes = ProcessBuilder.startPipeline(builders);
|
||||
processesRef.set(processes);
|
||||
Process last = processes.get(processes.size()-1);
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(last.getInputStream()))) {
|
||||
String line;
|
||||
|
|
@ -741,6 +761,8 @@ public class Script implements Callable<String> {
|
|||
result.second(ERR_TIMEOUT);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
LOGGER.error("Error executing piped commands", e);
|
||||
} finally {
|
||||
cleanupProcesses(processesRef);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue