mirror of https://github.com/apache/cloudstack.git
Compare commits
11 Commits
33200fcb69
...
e544fcac8a
| Author | SHA1 | Date |
|---|---|---|
|
|
e544fcac8a | |
|
|
bce3e54a7e | |
|
|
6a9835904c | |
|
|
6846619a6f | |
|
|
d1eb2822d9 | |
|
|
4676fb1090 | |
|
|
4fc90c123d | |
|
|
99dca0862a | |
|
|
f317e9cc16 | |
|
|
c7d282cd2e | |
|
|
10b60c27a0 |
|
|
@ -78,6 +78,7 @@ public class UpdateNetworkOfferingCmd extends BaseCmd {
|
||||||
|
|
||||||
@Parameter(name = ApiConstants.DOMAIN_ID,
|
@Parameter(name = ApiConstants.DOMAIN_ID,
|
||||||
type = CommandType.STRING,
|
type = CommandType.STRING,
|
||||||
|
length = 4096,
|
||||||
description = "The ID of the containing domain(s) as comma separated string, public for public offerings")
|
description = "The ID of the containing domain(s) as comma separated string, public for public offerings")
|
||||||
private String domainIds;
|
private String domainIds;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ public class UpdateDiskOfferingCmd extends BaseCmd {
|
||||||
@Parameter(name = ApiConstants.ZONE_ID,
|
@Parameter(name = ApiConstants.ZONE_ID,
|
||||||
type = CommandType.STRING,
|
type = CommandType.STRING,
|
||||||
description = "The ID of the containing zone(s) as comma separated string, all for all zones offerings",
|
description = "The ID of the containing zone(s) as comma separated string, all for all zones offerings",
|
||||||
|
length = 4096,
|
||||||
since = "4.13")
|
since = "4.13")
|
||||||
private String zoneIds;
|
private String zoneIds;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ public class UpdateServiceOfferingCmd extends BaseCmd {
|
||||||
@Parameter(name = ApiConstants.ZONE_ID,
|
@Parameter(name = ApiConstants.ZONE_ID,
|
||||||
type = CommandType.STRING,
|
type = CommandType.STRING,
|
||||||
description = "The ID of the containing zone(s) as comma separated string, all for all zones offerings",
|
description = "The ID of the containing zone(s) as comma separated string, all for all zones offerings",
|
||||||
|
length = 4096,
|
||||||
since = "4.13")
|
since = "4.13")
|
||||||
private String zoneIds;
|
private String zoneIds;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ public class UpdateVPCOfferingCmd extends BaseAsyncCmd {
|
||||||
@Parameter(name = ApiConstants.ZONE_ID,
|
@Parameter(name = ApiConstants.ZONE_ID,
|
||||||
type = CommandType.STRING,
|
type = CommandType.STRING,
|
||||||
description = "The ID of the containing zone(s) as comma separated string, all for all zones offerings",
|
description = "The ID of the containing zone(s) as comma separated string, all for all zones offerings",
|
||||||
|
length = 4096,
|
||||||
since = "4.13")
|
since = "4.13")
|
||||||
private String zoneIds;
|
private String zoneIds;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,6 @@ public interface VMScheduledJobDao extends GenericDao<VMScheduledJobVO, Long> {
|
||||||
int expungeJobsForSchedules(List<Long> scheduleId, Date dateAfter);
|
int expungeJobsForSchedules(List<Long> scheduleId, Date dateAfter);
|
||||||
|
|
||||||
int expungeJobsBefore(Date currentTimestamp);
|
int expungeJobsBefore(Date currentTimestamp);
|
||||||
|
|
||||||
|
VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long
|
||||||
|
|
||||||
private final SearchBuilder<VMScheduledJobVO> expungeJobForScheduleSearch;
|
private final SearchBuilder<VMScheduledJobVO> expungeJobForScheduleSearch;
|
||||||
|
|
||||||
|
private final SearchBuilder<VMScheduledJobVO> scheduleAndTimestampSearch;
|
||||||
|
|
||||||
static final String SCHEDULED_TIMESTAMP = "scheduled_timestamp";
|
static final String SCHEDULED_TIMESTAMP = "scheduled_timestamp";
|
||||||
|
|
||||||
static final String VM_SCHEDULE_ID = "vm_schedule_id";
|
static final String VM_SCHEDULE_ID = "vm_schedule_id";
|
||||||
|
|
@ -58,6 +60,11 @@ public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long
|
||||||
expungeJobForScheduleSearch.and(VM_SCHEDULE_ID, expungeJobForScheduleSearch.entity().getVmScheduleId(), SearchCriteria.Op.IN);
|
expungeJobForScheduleSearch.and(VM_SCHEDULE_ID, expungeJobForScheduleSearch.entity().getVmScheduleId(), SearchCriteria.Op.IN);
|
||||||
expungeJobForScheduleSearch.and(SCHEDULED_TIMESTAMP, expungeJobForScheduleSearch.entity().getScheduledTime(), SearchCriteria.Op.GTEQ);
|
expungeJobForScheduleSearch.and(SCHEDULED_TIMESTAMP, expungeJobForScheduleSearch.entity().getScheduledTime(), SearchCriteria.Op.GTEQ);
|
||||||
expungeJobForScheduleSearch.done();
|
expungeJobForScheduleSearch.done();
|
||||||
|
|
||||||
|
scheduleAndTimestampSearch = createSearchBuilder();
|
||||||
|
scheduleAndTimestampSearch.and(VM_SCHEDULE_ID, scheduleAndTimestampSearch.entity().getVmScheduleId(), SearchCriteria.Op.EQ);
|
||||||
|
scheduleAndTimestampSearch.and(SCHEDULED_TIMESTAMP, scheduleAndTimestampSearch.entity().getScheduledTime(), SearchCriteria.Op.EQ);
|
||||||
|
scheduleAndTimestampSearch.done();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,4 +99,12 @@ public class VMScheduledJobDaoImpl extends GenericDaoBase<VMScheduledJobVO, Long
|
||||||
sc.setParameters(SCHEDULED_TIMESTAMP, date);
|
sc.setParameters(SCHEDULED_TIMESTAMP, date);
|
||||||
return expunge(sc);
|
return expunge(sc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) {
|
||||||
|
SearchCriteria<VMScheduledJobVO> sc = scheduleAndTimestampSearch.create();
|
||||||
|
sc.setParameters(VM_SCHEDULE_ID, scheduleId);
|
||||||
|
sc.setParameters(SCHEDULED_TIMESTAMP, scheduledTimestamp);
|
||||||
|
return findOneBy(sc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,13 @@ public class VMSchedulerImpl extends ManagerBase implements VMScheduler, Configu
|
||||||
}
|
}
|
||||||
|
|
||||||
Date scheduledDateTime = Date.from(ts.toInstant());
|
Date scheduledDateTime = Date.from(ts.toInstant());
|
||||||
VMScheduledJobVO scheduledJob = new VMScheduledJobVO(vmSchedule.getVmId(), vmSchedule.getId(), vmSchedule.getAction(), scheduledDateTime);
|
VMScheduledJobVO scheduledJob = vmScheduledJobDao.findByScheduleAndTimestamp(vmSchedule.getId(), scheduledDateTime);
|
||||||
|
if (scheduledJob != null) {
|
||||||
|
logger.trace("Job is already scheduled for schedule {} at {}", vmSchedule, scheduledDateTime);
|
||||||
|
return scheduledDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduledJob = new VMScheduledJobVO(vmSchedule.getVmId(), vmSchedule.getId(), vmSchedule.getAction(), scheduledDateTime);
|
||||||
try {
|
try {
|
||||||
vmScheduledJobDao.persist(scheduledJob);
|
vmScheduledJobDao.persist(scheduledJob);
|
||||||
ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(), actionEventMap.get(vmSchedule.getAction()),
|
ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(), actionEventMap.get(vmSchedule.getAction()),
|
||||||
|
|
|
||||||
|
|
@ -18,52 +18,44 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<div class="form__item" :class="{'error': domainError}">
|
<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="form__label">{{ $t('label.domain') }}<span class="required">*</span></p>
|
<p class="required required-label">{{ $t('label.required') }}</p>
|
||||||
<p class="required required-label">{{ $t('label.required') }}</p>
|
<infinite-scroll-select
|
||||||
<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
|
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="handleChangeAccount"
|
v-model:value="domainId"
|
||||||
showSearch
|
api="listDomains"
|
||||||
optionFilterProp="value"
|
:apiParams="domainsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="domain"
|
||||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}" >
|
optionLabelKey="path"
|
||||||
<a-select-option v-for="(account, index) in accountsList" :value="account.name" :key="index">
|
defaultIcon="block-outlined"
|
||||||
{{ account.name }}
|
v-focus="true"
|
||||||
</a-select-option>
|
@change-option-value="handleChangeDomain" />
|
||||||
</a-select>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { api } from '@/api'
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DedicateDomain',
|
name: 'DedicateDomain',
|
||||||
|
components: {
|
||||||
|
InfiniteScrollSelect
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
error: {
|
error: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -72,59 +64,48 @@ export default {
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
domainsLoading: false,
|
|
||||||
domainId: null,
|
domainId: null,
|
||||||
accountsList: null,
|
selectedAccount: null,
|
||||||
domainsList: null,
|
|
||||||
domainError: false
|
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: {
|
watch: {
|
||||||
error () {
|
error () {
|
||||||
this.domainError = this.error
|
this.domainError = this.error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.fetchData()
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData () {
|
handleChangeDomain (domainId) {
|
||||||
this.domainsLoading = true
|
this.domainId = domainId
|
||||||
api('listDomains', {
|
this.selectedAccount = null
|
||||||
listAll: true,
|
this.$emit('domainChange', domainId)
|
||||||
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)
|
|
||||||
this.domainError = false
|
this.domainError = false
|
||||||
this.fetchAccounts()
|
|
||||||
},
|
},
|
||||||
handleChangeAccount (e) {
|
handleChangeAccount (accountName) {
|
||||||
this.$emit('accountChange', e)
|
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'
|
- 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'
|
- 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
|
- 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
|
- 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'
|
- 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:
|
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
|
- @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"
|
:filter-option="false"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
show-search
|
show-search
|
||||||
|
:allowClear="allowClear"
|
||||||
placeholder="Select"
|
placeholder="Select"
|
||||||
@search="onSearchTimed"
|
@search="onSearchTimed"
|
||||||
@popupScroll="onScroll"
|
@popupScroll="onScroll"
|
||||||
|
|
@ -75,9 +78,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
||||||
<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"/>
|
<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" />
|
<render-icon v-else :icon="defaultIcon" style="margin-right: 5px" />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -124,6 +127,10 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
allowClear: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
showIcon: {
|
showIcon: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
|
@ -135,6 +142,10 @@ export default {
|
||||||
pageSize: {
|
pageSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
selectFirstOption: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
|
|
@ -147,7 +158,8 @@ export default {
|
||||||
searchTimer: null,
|
searchTimer: null,
|
||||||
scrollHandlerAttached: false,
|
scrollHandlerAttached: false,
|
||||||
preselectedOptionValue: null,
|
preselectedOptionValue: null,
|
||||||
successiveFetches: 0
|
successiveFetches: 0,
|
||||||
|
hasAutoSelectedFirst: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
|
@ -166,6 +178,36 @@ export default {
|
||||||
},
|
},
|
||||||
formattedSearchFooterMessage () {
|
formattedSearchFooterMessage () {
|
||||||
return `${this.$t('label.showing.results.for').replace('%x', this.searchQuery)}`
|
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: {
|
watch: {
|
||||||
|
|
@ -210,6 +252,7 @@ export default {
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
if (this.successiveFetches === 0) {
|
if (this.successiveFetches === 0) {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
this.autoSelectFirstOptionIfNeeded()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -220,11 +263,10 @@ export default {
|
||||||
this.resetPreselectedOptionValue()
|
this.resetPreselectedOptionValue()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const matchValue = Array.isArray(this.preselectedOptionValue) ? this.preselectedOptionValue[0] : this.preselectedOptionValue
|
if (!this.preselectedMatch) {
|
||||||
const match = this.options.find(entry => entry[this.optionValueKey] === matchValue)
|
|
||||||
if (!match) {
|
|
||||||
this.successiveFetches++
|
this.successiveFetches++
|
||||||
if (this.options.length < this.totalCount) {
|
// Exclude defaultOption from count when comparing with totalCount
|
||||||
|
if (this.apiOptionsCount < this.totalCount) {
|
||||||
this.fetchItems()
|
this.fetchItems()
|
||||||
} else {
|
} else {
|
||||||
this.resetPreselectedOptionValue()
|
this.resetPreselectedOptionValue()
|
||||||
|
|
@ -246,6 +288,36 @@ export default {
|
||||||
this.preselectedOptionValue = null
|
this.preselectedOptionValue = null
|
||||||
this.successiveFetches = 0
|
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) {
|
onSearchTimed (value) {
|
||||||
clearTimeout(this.searchTimer)
|
clearTimeout(this.searchTimer)
|
||||||
this.searchTimer = setTimeout(() => {
|
this.searchTimer = setTimeout(() => {
|
||||||
|
|
@ -264,7 +336,8 @@ export default {
|
||||||
},
|
},
|
||||||
onScroll (e) {
|
onScroll (e) {
|
||||||
const nearBottom = e.target.scrollTop + e.target.clientHeight >= e.target.scrollHeight - 10
|
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) {
|
if (nearBottom && hasMore && !this.loading) {
|
||||||
this.fetchItems()
|
this.fetchItems()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -218,18 +218,19 @@ export const notifierPlugin = {
|
||||||
if (error.response.status) {
|
if (error.response.status) {
|
||||||
msg = `${i18n.global.t('message.request.failed')} (${error.response.status})`
|
msg = `${i18n.global.t('message.request.failed')} (${error.response.status})`
|
||||||
}
|
}
|
||||||
if (error.message) {
|
if (error.response.headers?.['x-description']) {
|
||||||
desc = error.message
|
|
||||||
}
|
|
||||||
if (error.response.headers && 'x-description' in error.response.headers) {
|
|
||||||
desc = error.response.headers['x-description']
|
desc = error.response.headers['x-description']
|
||||||
}
|
} else if (error.response.data) {
|
||||||
if (desc === '' && error.response.data) {
|
|
||||||
const responseKey = _.findKey(error.response.data, 'errortext')
|
const responseKey = _.findKey(error.response.data, 'errortext')
|
||||||
if (responseKey) {
|
if (responseKey) {
|
||||||
desc = error.response.data[responseKey].errortext
|
desc = error.response.data[responseKey].errortext
|
||||||
|
} else if (typeof error.response.data === 'string') {
|
||||||
|
desc = error.response.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!desc && error.message) {
|
||||||
|
desc = error.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let countNotify = store.getters.countNotify
|
let countNotify = store.getters.countNotify
|
||||||
countNotify++
|
countNotify++
|
||||||
|
|
|
||||||
|
|
@ -90,45 +90,31 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
|
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
:loading="domainLoading"
|
|
||||||
v-model:value="form.domainid"
|
v-model:value="form.domainid"
|
||||||
|
api="listDomains"
|
||||||
|
:apiParams="domainsApiParams"
|
||||||
|
resourceType="domain"
|
||||||
|
optionValueKey="id"
|
||||||
|
optionLabelKey="path"
|
||||||
|
defaultIcon="block-outlined"
|
||||||
|
:selectFirstOption="true"
|
||||||
:placeholder="apiParams.domainid.description"
|
:placeholder="apiParams.domainid.description"
|
||||||
showSearch
|
@change-option-value="handleDomainChange" />
|
||||||
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>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="account" ref="account" v-if="!account">
|
<a-form-item name="account" ref="account" v-if="!account">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.account"
|
v-model:value="form.account"
|
||||||
:loading="loadingAccount"
|
api="listAccounts"
|
||||||
:placeholder="apiParams.account.description"
|
:apiParams="accountsApiParams"
|
||||||
showSearch
|
resourceType="account"
|
||||||
optionFilterProp="label"
|
optionValueKey="name"
|
||||||
:filterOption="(input, option) => {
|
optionLabelKey="name"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
defaultIcon="team-outlined"
|
||||||
}" >
|
:placeholder="apiParams.account.description" />
|
||||||
<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>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="timezone" ref="timezone">
|
<a-form-item name="timezone" ref="timezone">
|
||||||
<template #label>
|
<template #label>
|
||||||
|
|
@ -185,12 +171,14 @@ import { timeZone } from '@/utils/timezone'
|
||||||
import debounce from 'lodash/debounce'
|
import debounce from 'lodash/debounce'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddUser',
|
name: 'AddUser',
|
||||||
components: {
|
components: {
|
||||||
TooltipLabel,
|
TooltipLabel,
|
||||||
ResourceIcon
|
ResourceIcon,
|
||||||
|
InfiniteScrollSelect
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
|
this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
|
||||||
|
|
@ -198,14 +186,9 @@ export default {
|
||||||
loading: false,
|
loading: false,
|
||||||
timeZoneLoading: false,
|
timeZoneLoading: false,
|
||||||
timeZoneMap: [],
|
timeZoneMap: [],
|
||||||
domainLoading: false,
|
|
||||||
domainsList: [],
|
|
||||||
selectedDomain: '',
|
|
||||||
samlEnable: false,
|
samlEnable: false,
|
||||||
idpLoading: false,
|
idpLoading: false,
|
||||||
idps: [],
|
idps: [],
|
||||||
loadingAccount: false,
|
|
||||||
accountList: [],
|
|
||||||
account: null,
|
account: null,
|
||||||
domainid: null
|
domainid: null
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +201,19 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
samlAllowed () {
|
samlAllowed () {
|
||||||
return 'authorizeSamlSso' in this.$store.getters.apis
|
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: {
|
methods: {
|
||||||
|
|
@ -241,53 +237,18 @@ export default {
|
||||||
fetchData () {
|
fetchData () {
|
||||||
this.account = this.$route.query && this.$route.query.account ? this.$route.query.account : null
|
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
|
this.domainid = this.$route.query && this.$route.query.domainid ? this.$route.query.domainid : null
|
||||||
if (!this.domianid) {
|
// Set initial domain if provided from route
|
||||||
this.fetchDomains()
|
if (this.domainid) {
|
||||||
|
this.form.domainid = this.domainid
|
||||||
}
|
}
|
||||||
this.fetchTimeZone()
|
this.fetchTimeZone()
|
||||||
if (this.samlAllowed) {
|
if (this.samlAllowed) {
|
||||||
this.fetchIdps()
|
this.fetchIdps()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fetchDomains () {
|
handleDomainChange (domainId) {
|
||||||
this.domainLoading = true
|
this.form.domainid = domainId
|
||||||
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 = []
|
|
||||||
this.form.account = null
|
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) {
|
fetchTimeZone (value) {
|
||||||
this.timeZoneMap = []
|
this.timeZoneMap = []
|
||||||
|
|
@ -328,12 +289,14 @@ export default {
|
||||||
accounttype: 0
|
accounttype: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Account: use route query account if available, otherwise use form value (which is the account name)
|
||||||
if (this.account) {
|
if (this.account) {
|
||||||
params.account = this.account
|
params.account = this.account
|
||||||
} else if (this.accountList[values.account]) {
|
} else if (values.account) {
|
||||||
params.account = this.accountList[values.account].name
|
params.account = values.account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Domain: use route query domainid if available, otherwise use form value
|
||||||
if (this.domainid) {
|
if (this.domainid) {
|
||||||
params.domainid = this.domainid
|
params.domainid = this.domainid
|
||||||
} else if (values.domainid) {
|
} else if (values.domainid) {
|
||||||
|
|
|
||||||
|
|
@ -638,11 +638,7 @@ export default {
|
||||||
this.$emit('refresh-data')
|
this.$emit('refresh-data')
|
||||||
this.closeAction()
|
this.closeAction()
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
this.$notifyError(e)
|
||||||
message: this.$t('message.upload.failed'),
|
|
||||||
description: `${this.$t('message.upload.template.failed.description')} - ${e}`,
|
|
||||||
duration: 0
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchCustomHypervisorName () {
|
fetchCustomHypervisorName () {
|
||||||
|
|
|
||||||
|
|
@ -121,15 +121,20 @@
|
||||||
ref="domain"
|
ref="domain"
|
||||||
name="domain"
|
name="domain"
|
||||||
>
|
>
|
||||||
<a-auto-complete
|
<infinite-scroll-select
|
||||||
v-model:value="form.domain"
|
v-model:value="form.domain"
|
||||||
:options="domains"
|
api="listDomains"
|
||||||
|
:apiParams="domainsApiParams"
|
||||||
|
resourceType="domain"
|
||||||
|
optionValueKey="id"
|
||||||
|
optionLabelKey="path"
|
||||||
|
defaultIcon="block-outlined"
|
||||||
:placeholder="$t('label.domain')"
|
:placeholder="$t('label.domain')"
|
||||||
:filter-option="filterOption"
|
:defaultOption="{ id: null, path: ''}"
|
||||||
|
:allowClear="true"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
@select="getAccounts"
|
@change-option-value="handleDomainChange"
|
||||||
:dropdownMatchSelectWidth="false"
|
@change-option="handleDomainOptionChange" />
|
||||||
/>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
@ -150,15 +155,20 @@
|
||||||
ref="account"
|
ref="account"
|
||||||
name="account"
|
name="account"
|
||||||
>
|
>
|
||||||
<a-auto-complete
|
<infinite-scroll-select
|
||||||
v-model:value="form.account"
|
v-model:value="form.account"
|
||||||
:options="accounts"
|
api="listAccounts"
|
||||||
|
:apiParams="accountsApiParams"
|
||||||
|
resourceType="account"
|
||||||
|
optionValueKey="id"
|
||||||
|
optionLabelKey="name"
|
||||||
|
defaultIcon="team-outlined"
|
||||||
:placeholder="$t('label.account')"
|
:placeholder="$t('label.account')"
|
||||||
:filter-option="filterOption"
|
|
||||||
:disabled="form.isRecursive"
|
:disabled="form.isRecursive"
|
||||||
:dropdownMatchSelectWidth="false"
|
:defaultOption="{ id: null, name: ''}"
|
||||||
@select="selectAccount"
|
allowClear="true"
|
||||||
/>
|
@change-option-value="selectAccount"
|
||||||
|
@change-option="selectAccountOption" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="3" v-if="'listUsageTypes' in $store.getters.apis">
|
<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 TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
import TooltipButton from '@/components/widgets/TooltipButton'
|
import TooltipButton from '@/components/widgets/TooltipButton'
|
||||||
import Status from '@/components/widgets/Status'
|
import Status from '@/components/widgets/Status'
|
||||||
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
@ -374,7 +385,8 @@ export default {
|
||||||
ListView,
|
ListView,
|
||||||
Status,
|
Status,
|
||||||
TooltipLabel,
|
TooltipLabel,
|
||||||
TooltipButton
|
TooltipButton,
|
||||||
|
InfiniteScrollSelect
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
resource: {
|
resource: {
|
||||||
|
|
@ -402,8 +414,6 @@ export default {
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
usageTypes: [],
|
usageTypes: [],
|
||||||
domains: [],
|
|
||||||
accounts: [],
|
|
||||||
account: null,
|
account: null,
|
||||||
domain: null,
|
domain: null,
|
||||||
usageType: null,
|
usageType: null,
|
||||||
|
|
@ -436,6 +446,23 @@ export default {
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
this.updateColumns()
|
this.updateColumns()
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
domainsApiParams () {
|
||||||
|
return {
|
||||||
|
listall: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accountsApiParams () {
|
||||||
|
if (!this.form.domain) {
|
||||||
|
return {
|
||||||
|
listall: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
domainid: this.form.domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clearFilters () {
|
clearFilters () {
|
||||||
this.formRef.value.resetFields()
|
this.formRef.value.resetFields()
|
||||||
|
|
@ -445,8 +472,6 @@ export default {
|
||||||
this.usageType = null
|
this.usageType = null
|
||||||
this.page = 1
|
this.page = 1
|
||||||
this.pageSize = 20
|
this.pageSize = 20
|
||||||
|
|
||||||
this.getAccounts()
|
|
||||||
},
|
},
|
||||||
disabledDate (current) {
|
disabledDate (current) {
|
||||||
return current && current > dayjs().endOf('day')
|
return current && current > dayjs().endOf('day')
|
||||||
|
|
@ -473,8 +498,6 @@ export default {
|
||||||
this.listUsageServerMetrics()
|
this.listUsageServerMetrics()
|
||||||
this.getUsageTypes()
|
this.getUsageTypes()
|
||||||
this.getAllUsageRecordColumns()
|
this.getAllUsageRecordColumns()
|
||||||
this.getDomains()
|
|
||||||
this.getAccounts()
|
|
||||||
if (!this.$store.getters.customColumns[this.$store.getters.userInfo.id]) {
|
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.$store.getters.customColumns[this.$store.getters.userInfo.id][this.$route.path] = this.selectedColumnKeys
|
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)
|
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) {
|
selectUsageType (value, option) {
|
||||||
if (option && option.id) {
|
if (option && option.id) {
|
||||||
this.usageType = option
|
this.usageType = option
|
||||||
|
|
@ -548,24 +561,12 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDomains () {
|
handleDomainChange (domainId) {
|
||||||
api('listDomains', { listAll: true }).then(json => {
|
this.form.domain = domainId
|
||||||
if (json && json.listdomainsresponse && json.listdomainsresponse.domain) {
|
this.form.account = null
|
||||||
this.domains = [{ id: null, value: '' }, ...json.listdomainsresponse.domain.map(x => {
|
|
||||||
return {
|
|
||||||
id: x.id,
|
|
||||||
value: x.path
|
|
||||||
}
|
|
||||||
})]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
getAccounts (value, option) {
|
handleDomainOptionChange (option) {
|
||||||
var params = {
|
|
||||||
listAll: true
|
|
||||||
}
|
|
||||||
if (option && option.id) {
|
if (option && option.id) {
|
||||||
params.domainid = option.id
|
|
||||||
this.domain = option
|
this.domain = option
|
||||||
} else {
|
} else {
|
||||||
this.domain = null
|
this.domain = null
|
||||||
|
|
@ -573,16 +574,19 @@ export default {
|
||||||
this.formRef.value.resetFields('domain')
|
this.formRef.value.resetFields('domain')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api('listAccounts', params).then(json => {
|
},
|
||||||
if (json && json.listaccountsresponse && json.listaccountsresponse.account) {
|
selectAccount (accountId) {
|
||||||
this.accounts = [{ id: null, value: '' }, ...json.listaccountsresponse.account.map(x => {
|
this.form.account = accountId
|
||||||
return {
|
},
|
||||||
id: x.id,
|
selectAccountOption (option) {
|
||||||
value: x.name
|
if (option && option.id) {
|
||||||
}
|
this.account = option
|
||||||
})]
|
} else {
|
||||||
|
this.account = null
|
||||||
|
if (this.formRef?.value) {
|
||||||
|
this.formRef.value.resetFields('account')
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
},
|
},
|
||||||
getParams (page, pageSize) {
|
getParams (page, pageSize) {
|
||||||
const formRaw = toRaw(this.form)
|
const formRaw = toRaw(this.form)
|
||||||
|
|
|
||||||
|
|
@ -73,42 +73,32 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
|
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.domainid"
|
v-model:value="form.domainid"
|
||||||
showSearch
|
api="listDomains"
|
||||||
optionFilterProp="label"
|
:apiParams="domainsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="domain"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}"
|
optionLabelKey="path"
|
||||||
:loading="domainLoading"
|
defaultIcon="block-outlined"
|
||||||
|
allowClear="true"
|
||||||
:placeholder="apiParams.domainid.description"
|
:placeholder="apiParams.domainid.description"
|
||||||
@change="val => { handleDomainChange(val) }">
|
@change-option-value="handleDomainChange" />
|
||||||
<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>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="account" ref="account" v-if="domainid">
|
<a-form-item name="account" ref="account" v-if="domainid">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.account"
|
v-model:value="form.account"
|
||||||
showSearch
|
api="listAccounts"
|
||||||
optionFilterProp="label"
|
:apiParams="accountsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="account"
|
||||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="name"
|
||||||
}"
|
optionLabelKey="name"
|
||||||
:placeholder="apiParams.account.description"
|
defaultIcon="team-outlined"
|
||||||
@change="val => { handleAccountChange(val) }">
|
allowClear="true"
|
||||||
<a-select-option v-for="(acc, index) in accounts" :value="acc.name" :key="index">
|
:placeholder="apiParams.account.description" />
|
||||||
{{ acc.name }}
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item
|
<a-form-item
|
||||||
|
|
@ -199,13 +189,15 @@ import { api } from '@/api'
|
||||||
import { mixinForm } from '@/utils/mixin'
|
import { mixinForm } from '@/utils/mixin'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateTemplate',
|
name: 'CreateTemplate',
|
||||||
mixins: [mixinForm],
|
mixins: [mixinForm],
|
||||||
components: {
|
components: {
|
||||||
ResourceIcon,
|
ResourceIcon,
|
||||||
TooltipLabel
|
TooltipLabel,
|
||||||
|
InfiniteScrollSelect
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
resource: {
|
resource: {
|
||||||
|
|
@ -219,9 +211,6 @@ export default {
|
||||||
zones: [],
|
zones: [],
|
||||||
osTypes: {},
|
osTypes: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
domains: [],
|
|
||||||
accounts: [],
|
|
||||||
domainLoading: false,
|
|
||||||
domainid: null,
|
domainid: null,
|
||||||
account: null,
|
account: null,
|
||||||
architectureTypes: {}
|
architectureTypes: {}
|
||||||
|
|
@ -230,6 +219,21 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
isAdminRole () {
|
isAdminRole () {
|
||||||
return this.$store.getters.userInfo.roletype === 'Admin'
|
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 () {
|
beforeCreate () {
|
||||||
|
|
@ -256,9 +260,6 @@ export default {
|
||||||
if (this.resource.intervaltype) {
|
if (this.resource.intervaltype) {
|
||||||
this.fetchSnapshotZones()
|
this.fetchSnapshotZones()
|
||||||
}
|
}
|
||||||
if ('listDomains' in this.$store.getters.apis) {
|
|
||||||
this.fetchDomains()
|
|
||||||
}
|
|
||||||
this.architectureTypes.opts = this.$fetchCpuArchitectureTypes()
|
this.architectureTypes.opts = this.$fetchCpuArchitectureTypes()
|
||||||
},
|
},
|
||||||
fetchOsTypes () {
|
fetchOsTypes () {
|
||||||
|
|
@ -309,44 +310,16 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchDomains () {
|
handleDomainChange (domainId) {
|
||||||
const params = {}
|
this.domainid = domainId
|
||||||
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
|
|
||||||
this.form.account = null
|
this.form.account = null
|
||||||
this.account = null
|
this.account = null
|
||||||
if ('listAccounts' in this.$store.getters.apis) {
|
|
||||||
await this.fetchAccounts()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
fetchAccounts () {
|
handleAccountChange (accountName) {
|
||||||
return new Promise((resolve, reject) => {
|
if (accountName) {
|
||||||
api('listAccounts', {
|
this.account = accountName
|
||||||
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
|
|
||||||
} else {
|
} else {
|
||||||
this.account = acc
|
this.account = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSubmit (e) {
|
handleSubmit (e) {
|
||||||
|
|
|
||||||
|
|
@ -57,43 +57,33 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.zoneId"
|
v-model:value="form.zoneId"
|
||||||
showSearch
|
api="listZones"
|
||||||
optionFilterProp="label"
|
:apiParams="zonesApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="zone"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}" >
|
optionLabelKey="name"
|
||||||
<a-select-option :value="zone.id" v-for="zone in zones" :key="zone.id" :label="zone.name || zone.description">
|
defaultIcon="global-outlined"
|
||||||
<span>
|
selectFirstOption="true"
|
||||||
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
|
@change-option-value="handleZoneChange" />
|
||||||
<global-outlined v-else style="margin-right: 5px"/>
|
|
||||||
{{ zone.name || zone.description }}
|
|
||||||
</span>
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="diskofferingid" ref="diskofferingid">
|
<a-form-item name="diskofferingid" ref="diskofferingid">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description"/>
|
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.diskofferingid"
|
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"
|
:placeholder="apiParams.diskofferingid.description"
|
||||||
showSearch
|
@change-option="onChangeDiskOffering" />
|
||||||
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>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item ref="format" name="format">
|
<a-form-item ref="format" name="format">
|
||||||
<template #label>
|
<template #label>
|
||||||
|
|
@ -124,38 +114,33 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.domain')" :tooltip="apiParams.domainid.description"/>
|
<tooltip-label :title="$t('label.domain')" :tooltip="apiParams.domainid.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.domainid"
|
v-model:value="form.domainid"
|
||||||
showSearch
|
api="listDomains"
|
||||||
optionFilterProp="label"
|
:apiParams="domainsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="domain"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}"
|
optionLabelKey="path"
|
||||||
:loading="domainLoading"
|
defaultIcon="block-outlined"
|
||||||
:placeholder="$t('label.domainid')"
|
:placeholder="$t('label.domainid')"
|
||||||
@change="val => { handleDomainChange(domainList[val].id) }">
|
allowClear="true"
|
||||||
<a-select-option v-for="(opt, optIndex) in domainList" :key="optIndex" :label="opt.path || opt.name || opt.description">
|
@change-option-value="handleDomainChange" />
|
||||||
{{ opt.path || opt.name || opt.description }}
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="account" ref="account" v-if="'listDomains' in $store.getters.apis">
|
<a-form-item name="account" ref="account" v-if="'listDomains' in $store.getters.apis">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.account"
|
v-model:value="form.account"
|
||||||
showSearch
|
api="listAccounts"
|
||||||
optionFilterProp="value"
|
:apiParams="accountsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="account"
|
||||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="name"
|
||||||
}"
|
optionLabelKey="name"
|
||||||
|
defaultIcon="team-outlined"
|
||||||
|
allowClear="true"
|
||||||
:placeholder="$t('label.account')"
|
:placeholder="$t('label.account')"
|
||||||
@change="val => { handleAccountChange(val) }">
|
@change-option-value="handleAccountChange" />
|
||||||
<a-select-option v-for="(acc, index) in accountList" :value="acc.name" :key="index">
|
|
||||||
{{ acc.name }}
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<div :span="24" class="action-button">
|
<div :span="24" class="action-button">
|
||||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-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 { mixinForm } from '@/utils/mixin'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UploadLocalVolume',
|
name: 'UploadLocalVolume',
|
||||||
mixins: [mixinForm],
|
mixins: [mixinForm],
|
||||||
components: {
|
components: {
|
||||||
ResourceIcon,
|
ResourceIcon,
|
||||||
TooltipLabel
|
TooltipLabel,
|
||||||
|
InfiniteScrollSelect
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
fileList: [],
|
fileList: [],
|
||||||
zones: [],
|
|
||||||
domainList: [],
|
|
||||||
accountList: [],
|
|
||||||
offerings: [],
|
|
||||||
offeringLoading: false,
|
|
||||||
formats: ['RAW', 'VHD', 'VHDX', 'OVA', 'QCOW2'],
|
formats: ['RAW', 'VHD', 'VHDX', 'OVA', 'QCOW2'],
|
||||||
domainId: null,
|
domainId: null,
|
||||||
account: null,
|
account: null,
|
||||||
uploadParams: null,
|
uploadParams: null,
|
||||||
domainLoading: false,
|
customDiskOffering: false,
|
||||||
|
isCustomizedDiskIOps: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
uploadPercentage: 0
|
uploadPercentage: 0
|
||||||
}
|
}
|
||||||
|
|
@ -201,9 +184,38 @@ export default {
|
||||||
beforeCreate () {
|
beforeCreate () {
|
||||||
this.apiParams = this.$getApiParams('getUploadParamsForVolume')
|
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 () {
|
created () {
|
||||||
this.initForm()
|
this.initForm()
|
||||||
this.fetchData()
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initForm () {
|
initForm () {
|
||||||
|
|
@ -221,38 +233,18 @@ export default {
|
||||||
zoneId: [{ required: true, message: this.$t('message.error.select') }]
|
zoneId: [{ required: true, message: this.$t('message.error.select') }]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
listZones () {
|
handleZoneChange (zoneId) {
|
||||||
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) {
|
|
||||||
this.form.zoneId = zoneId
|
this.form.zoneId = zoneId
|
||||||
this.zoneId = zoneId
|
// InfiniteScrollSelect will auto-reload disk offerings when apiParams changes
|
||||||
this.fetchDiskOfferings(zoneId)
|
|
||||||
},
|
},
|
||||||
fetchDiskOfferings (zoneId) {
|
onChangeDiskOffering (offering) {
|
||||||
this.offeringLoading = true
|
if (offering) {
|
||||||
this.offerings = [{ id: -1, name: '' }]
|
this.customDiskOffering = offering.iscustomized || false
|
||||||
this.form.diskofferingid = undefined
|
this.isCustomizedDiskIOps = offering.iscustomizediops || false
|
||||||
api('listDiskOfferings', {
|
} else {
|
||||||
zoneid: zoneId,
|
this.customDiskOffering = false
|
||||||
listall: true
|
this.isCustomizedDiskIOps = false
|
||||||
}).then(json => {
|
}
|
||||||
for (var offering of json.listdiskofferingsresponse.diskoffering) {
|
|
||||||
if (offering.iscustomized) {
|
|
||||||
this.offerings.push(offering)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
|
||||||
this.offeringLoading = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
handleRemove (file) {
|
handleRemove (file) {
|
||||||
const index = this.fileList.indexOf(file)
|
const index = this.fileList.indexOf(file)
|
||||||
|
|
@ -266,53 +258,14 @@ export default {
|
||||||
this.form.file = file
|
this.form.file = file
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
handleDomainChange (domain) {
|
handleDomainChange (domainId) {
|
||||||
this.domainId = domain
|
this.form.domainid = domainId
|
||||||
if ('listAccounts' in this.$store.getters.apis) {
|
this.domainId = domainId
|
||||||
this.fetchAccounts()
|
this.form.account = null
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleAccountChange (acc) {
|
handleAccountChange (accountName) {
|
||||||
if (acc) {
|
this.form.account = accountName
|
||||||
this.account = acc.name
|
this.account = accountName
|
||||||
} 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)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
handleSubmit (e) {
|
handleSubmit (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
||||||
|
|
@ -47,21 +47,16 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.zoneId"
|
v-model:value="form.zoneId"
|
||||||
showSearch
|
api="listZones"
|
||||||
optionFilterProp="label"
|
:apiParams="zonesApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="zone"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}" >
|
optionLabelKey="name"
|
||||||
<a-select-option :value="zone.id" v-for="zone in zones" :key="zone.id" :label="zone.name || zone.description">
|
defaultIcon="global-outlined"
|
||||||
<span>
|
selectFirstOption="true"
|
||||||
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
|
@change-option-value="handleZoneChange" />
|
||||||
<global-outlined v-else style="margin-right: 5px"/>
|
|
||||||
{{ zone.name || zone.description }}
|
|
||||||
</span>
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="format" ref="format">
|
<a-form-item name="format" ref="format">
|
||||||
<template #label>
|
<template #label>
|
||||||
|
|
@ -83,23 +78,17 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description || $t('label.diskoffering')"/>
|
<tooltip-label :title="$t('label.diskofferingid')" :tooltip="apiParams.diskofferingid.description || $t('label.diskoffering')"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.diskofferingid"
|
v-model:value="form.diskofferingid"
|
||||||
:loading="loading"
|
api="listDiskOfferings"
|
||||||
@change="id => onChangeDiskOffering(id)"
|
:apiParams="diskOfferingsApiParams"
|
||||||
showSearch
|
resourceType="diskoffering"
|
||||||
optionFilterProp="label"
|
optionValueKey="id"
|
||||||
:filterOption="(input, option) => {
|
optionLabelKey="displaytext"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
defaultIcon="hdd-outlined"
|
||||||
}" >
|
:defaultOption="{ id: null, displaytext: ''}"
|
||||||
<a-select-option
|
allowClear="true"
|
||||||
v-for="(offering, index) in offerings"
|
@change-option="onChangeDiskOffering" />
|
||||||
:value="offering.id"
|
|
||||||
:key="index"
|
|
||||||
:label="offering.displaytext || offering.name">
|
|
||||||
{{ offering.displaytext || offering.name }}
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="checksum" ref="checksum">
|
<a-form-item name="checksum" ref="checksum">
|
||||||
<template #label>
|
<template #label>
|
||||||
|
|
@ -114,38 +103,33 @@
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.domain')" :tooltip="apiParams.domainid.description"/>
|
<tooltip-label :title="$t('label.domain')" :tooltip="apiParams.domainid.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.domainid"
|
v-model:value="form.domainid"
|
||||||
showSearch
|
api="listDomains"
|
||||||
optionFilterProp="label"
|
:apiParams="domainsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="domain"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}"
|
optionLabelKey="path"
|
||||||
:loading="domainLoading"
|
defaultIcon="block-outlined"
|
||||||
|
allowClear="true"
|
||||||
:placeholder="$t('label.domainid')"
|
:placeholder="$t('label.domainid')"
|
||||||
@change="val => { handleDomainChange(domainList[val].id) }">
|
@change-option-value="handleDomainChange" />
|
||||||
<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>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="account" ref="account" v-if="'listDomains' in $store.getters.apis">
|
<a-form-item name="account" ref="account" v-if="'listDomains' in $store.getters.apis">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.account"
|
v-model:value="form.account"
|
||||||
showSearch
|
api="listAccounts"
|
||||||
optionFilterProp="value"
|
:apiParams="accountsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="account"
|
||||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="name"
|
||||||
}"
|
optionLabelKey="name"
|
||||||
|
defaultIcon="team-outlined"
|
||||||
:placeholder="$t('label.account')"
|
:placeholder="$t('label.account')"
|
||||||
@change="val => { handleAccountChange(val) }">
|
allowClear="true"
|
||||||
<a-select-option v-for="(acc, index) in accountList" :value="acc.name" :key="index">
|
@change-option-value="handleAccountChange" />
|
||||||
{{ acc.name }}
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<div :span="24" class="action-button">
|
<div :span="24" class="action-button">
|
||||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||||
|
|
@ -162,27 +146,26 @@ import { api } from '@/api'
|
||||||
import { mixinForm } from '@/utils/mixin'
|
import { mixinForm } from '@/utils/mixin'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UploadVolume',
|
name: 'UploadVolume',
|
||||||
mixins: [mixinForm],
|
mixins: [mixinForm],
|
||||||
components: {
|
components: {
|
||||||
ResourceIcon,
|
ResourceIcon,
|
||||||
TooltipLabel
|
TooltipLabel,
|
||||||
|
InfiniteScrollSelect
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
zones: [],
|
|
||||||
domainList: [],
|
|
||||||
accountList: [],
|
|
||||||
formats: ['RAW', 'VHD', 'VHDX', 'OVA', 'QCOW2'],
|
formats: ['RAW', 'VHD', 'VHDX', 'OVA', 'QCOW2'],
|
||||||
offerings: [],
|
|
||||||
zoneSelected: '',
|
zoneSelected: '',
|
||||||
selectedDiskOfferingId: null,
|
selectedDiskOfferingId: null,
|
||||||
domainId: null,
|
domainId: null,
|
||||||
account: null,
|
account: null,
|
||||||
uploadParams: null,
|
uploadParams: null,
|
||||||
domainLoading: false,
|
customDiskOffering: false,
|
||||||
|
isCustomizedDiskIOps: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
uploadPercentage: 0
|
uploadPercentage: 0
|
||||||
}
|
}
|
||||||
|
|
@ -190,9 +173,36 @@ export default {
|
||||||
beforeCreate () {
|
beforeCreate () {
|
||||||
this.apiParams = this.$getApiParams('uploadVolume')
|
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 () {
|
created () {
|
||||||
this.initForm()
|
this.initForm()
|
||||||
this.fetchData()
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initForm () {
|
initForm () {
|
||||||
|
|
@ -207,78 +217,28 @@ export default {
|
||||||
format: [{ required: true, message: this.$t('message.error.select') }]
|
format: [{ required: true, message: this.$t('message.error.select') }]
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
fetchData () {
|
handleZoneChange (zoneId) {
|
||||||
this.loading = true
|
this.form.zoneId = zoneId
|
||||||
api('listZones', { showicon: true }).then(json => {
|
// InfiniteScrollSelect will auto-reload disk offerings when apiParams changes
|
||||||
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()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
fetchDiskOfferings (zoneId) {
|
onChangeDiskOffering (offering) {
|
||||||
this.loading = true
|
if (offering) {
|
||||||
api('listDiskOfferings', {
|
this.customDiskOffering = offering.iscustomized || false
|
||||||
zoneid: zoneId,
|
this.isCustomizedDiskIOps = offering.iscustomizediops || false
|
||||||
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
|
|
||||||
} else {
|
} 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) {
|
handleSubmit (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.loading) return
|
if (this.loading) return
|
||||||
|
|
|
||||||
|
|
@ -67,39 +67,33 @@
|
||||||
<info-circle-outlined style="color: rgba(0,0,0,.45)" />
|
<info-circle-outlined style="color: rgba(0,0,0,.45)" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
id="domain-selection"
|
id="domain-selection"
|
||||||
v-model:value="form.domainid"
|
v-model:value="form.domainid"
|
||||||
showSearch
|
api="listDomains"
|
||||||
optionFilterProp="label"
|
:apiParams="domainsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="domain"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}"
|
optionLabelKey="path"
|
||||||
:loading="domainLoading"
|
defaultIcon="block-outlined"
|
||||||
|
:defaultOption="{ id: null, path: ''}"
|
||||||
|
allowClear="true"
|
||||||
:placeholder="apiParams.domainid.description"
|
:placeholder="apiParams.domainid.description"
|
||||||
@change="val => { handleDomainChanged(val) }">
|
@change-option-value="handleDomainChanged" />
|
||||||
<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>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="account" ref="account" v-if="isAdminOrDomainAdmin && ['Local'].includes(form.scope) && form.domainid">
|
<a-form-item name="account" ref="account" v-if="isAdminOrDomainAdmin && ['Local'].includes(form.scope) && form.domainid">
|
||||||
<template #label>
|
<template #label>
|
||||||
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
<tooltip-label :title="$t('label.account')" :tooltip="apiParams.account.description"/>
|
||||||
</template>
|
</template>
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
v-model:value="form.account"
|
v-model:value="form.account"
|
||||||
showSearch
|
api="listAccounts"
|
||||||
optionFilterProp="label"
|
:apiParams="accountsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="account"
|
||||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="name"
|
||||||
}"
|
optionLabelKey="name"
|
||||||
:loading="accountLoading"
|
defaultIcon="team-outlined"
|
||||||
:placeholder="apiParams.account.description">
|
: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>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item name="payloadurl" ref="payloadurl">
|
<a-form-item name="payloadurl" ref="payloadurl">
|
||||||
<template #label>
|
<template #label>
|
||||||
|
|
@ -156,25 +150,22 @@
|
||||||
<script>
|
<script>
|
||||||
import { ref, reactive, toRaw } from 'vue'
|
import { ref, reactive, toRaw } from 'vue'
|
||||||
import { api } from '@/api'
|
import { api } from '@/api'
|
||||||
import _ from 'lodash'
|
|
||||||
import { mixinForm } from '@/utils/mixin'
|
import { mixinForm } from '@/utils/mixin'
|
||||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||||
import TestWebhookDeliveryView from '@/components/view/TestWebhookDeliveryView'
|
import TestWebhookDeliveryView from '@/components/view/TestWebhookDeliveryView'
|
||||||
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CreateWebhook',
|
name: 'CreateWebhook',
|
||||||
mixins: [mixinForm],
|
mixins: [mixinForm],
|
||||||
components: {
|
components: {
|
||||||
TooltipLabel,
|
TooltipLabel,
|
||||||
TestWebhookDeliveryView
|
TestWebhookDeliveryView,
|
||||||
|
InfiniteScrollSelect
|
||||||
},
|
},
|
||||||
props: {},
|
props: {},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
domains: [],
|
|
||||||
domainLoading: false,
|
|
||||||
accounts: [],
|
|
||||||
accountLoading: false,
|
|
||||||
loading: false,
|
loading: false,
|
||||||
testDeliveryAllowed: false,
|
testDeliveryAllowed: false,
|
||||||
testDeliveryLoading: false
|
testDeliveryLoading: false
|
||||||
|
|
@ -185,9 +176,6 @@ export default {
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.initForm()
|
this.initForm()
|
||||||
if (['Domain', 'Local'].includes(this.form.scope)) {
|
|
||||||
this.fetchDomainData()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isAdminOrDomainAdmin () {
|
isAdminOrDomainAdmin () {
|
||||||
|
|
@ -201,6 +189,21 @@ export default {
|
||||||
return this.form.payloadurl.toLowerCase().startsWith('https://')
|
return this.form.payloadurl.toLowerCase().startsWith('https://')
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
},
|
||||||
|
domainsApiParams () {
|
||||||
|
return {
|
||||||
|
listAll: true,
|
||||||
|
showicon: true,
|
||||||
|
details: 'min'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accountsApiParams () {
|
||||||
|
if (!this.form.domainid) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
domainid: this.form.domainid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -228,46 +231,6 @@ export default {
|
||||||
updateTestDeliveryLoading (value) {
|
updateTestDeliveryLoading (value) {
|
||||||
this.testDeliveryLoading = 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) {
|
handleSubmit (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (this.loading) return
|
if (this.loading) return
|
||||||
|
|
@ -300,10 +263,8 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (values.account) {
|
if (values.account) {
|
||||||
const accountItem = _.find(this.accounts, (option) => option.id === values.account)
|
// values.account is the account name (optionValueKey="name")
|
||||||
if (accountItem) {
|
params.account = values.account
|
||||||
params.account = accountItem.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.loading = true
|
this.loading = true
|
||||||
api('createWebhook', params).then(json => {
|
api('createWebhook', params).then(json => {
|
||||||
|
|
@ -331,14 +292,11 @@ export default {
|
||||||
}, 1)
|
}, 1)
|
||||||
},
|
},
|
||||||
handleScopeChange (e) {
|
handleScopeChange (e) {
|
||||||
if (['Domain', 'Local'].includes(this.form.scope)) {
|
this.form.domainid = null
|
||||||
this.fetchDomainData()
|
this.form.account = null
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handleDomainChanged (domainid) {
|
handleDomainChanged (domainid) {
|
||||||
if (domainid) {
|
this.form.account = null
|
||||||
this.fetchAccountData()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -372,22 +372,16 @@
|
||||||
name="domain"
|
name="domain"
|
||||||
ref="domain"
|
ref="domain"
|
||||||
:label="$t('label.domain')">
|
:label="$t('label.domain')">
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
@change="changeDomain"
|
|
||||||
v-model:value="importForm.selectedDomain"
|
v-model:value="importForm.selectedDomain"
|
||||||
showSearch
|
api="listDomains"
|
||||||
optionFilterProp="label"
|
:apiParams="domainsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="domain"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}" >
|
optionLabelKey="path"
|
||||||
<a-select-option v-for="domain in domains" :key="domain.name" :value="domain.id" :label="domain.path || domain.name || domain.description">
|
defaultIcon="block-outlined"
|
||||||
<span>
|
allowClear="true"
|
||||||
<resource-icon v-if="domain && domain.icon" :image="domain.icon.base64image" size="1x" style="margin-right: 5px"/>
|
@change-option-value="changeDomain" />
|
||||||
<block-outlined v-else style="margin-right: 5px" />
|
|
||||||
{{ domain.path || domain.name || domain.description }}
|
|
||||||
</span>
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item
|
<a-form-item
|
||||||
|
|
@ -395,22 +389,16 @@
|
||||||
name="account"
|
name="account"
|
||||||
ref="account"
|
ref="account"
|
||||||
:label="$t('label.account')">
|
:label="$t('label.account')">
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
@change="changeAccount"
|
|
||||||
v-model:value="importForm.selectedAccount"
|
v-model:value="importForm.selectedAccount"
|
||||||
showSearch
|
api="listAccounts"
|
||||||
optionFilterProp="value"
|
:apiParams="accountsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="account"
|
||||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="name"
|
||||||
}" >
|
optionLabelKey="name"
|
||||||
<a-select-option v-for="account in accounts" :key="account.name" :value="account.name">
|
defaultIcon="team-outlined"
|
||||||
<span>
|
allowClear="true"
|
||||||
<resource-icon v-if="account && account.icon" :image="account.icon.base64image" size="1x" style="margin-right: 5px"/>
|
@change-option-value="changeAccount" />
|
||||||
<team-outlined v-else style="margin-right: 5px" />
|
|
||||||
{{ account.name }}
|
|
||||||
</span>
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
<span v-if="importForm.accountError" class="required">{{ $t('label.required') }}</span>
|
<span v-if="importForm.accountError" class="required">{{ $t('label.required') }}</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
|
@ -419,22 +407,16 @@
|
||||||
name="project"
|
name="project"
|
||||||
ref="project"
|
ref="project"
|
||||||
:label="$t('label.project')">
|
:label="$t('label.project')">
|
||||||
<a-select
|
<infinite-scroll-select
|
||||||
@change="changeProject"
|
|
||||||
v-model:value="importForm.selectedProject"
|
v-model:value="importForm.selectedProject"
|
||||||
showSearch
|
api="listProjects"
|
||||||
optionFilterProp="label"
|
:apiParams="projectsApiParams"
|
||||||
:filterOption="(input, option) => {
|
resourceType="project"
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
optionValueKey="id"
|
||||||
}" >
|
optionLabelKey="name"
|
||||||
<a-select-option v-for="project in projects" :key="project.id" :value="project.id" :label="project.name">
|
defaultIcon="project-outlined"
|
||||||
<span>
|
allowClear="true"
|
||||||
<resource-icon v-if="project && project.icon" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
|
@change-option-value="changeProject" />
|
||||||
<project-outlined v-else style="margin-right: 5px" />
|
|
||||||
{{ project.name }}
|
|
||||||
</span>
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
<span v-if="importForm.projectError" class="required">{{ $t('label.required') }}</span>
|
<span v-if="importForm.projectError" class="required">{{ $t('label.required') }}</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
|
@ -480,6 +462,7 @@ import Status from '@/components/widgets/Status'
|
||||||
import SearchView from '@/components/view/SearchView'
|
import SearchView from '@/components/view/SearchView'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import TooltipLabel from '@/components/widgets/TooltipLabel.vue'
|
import TooltipLabel from '@/components/widgets/TooltipLabel.vue'
|
||||||
|
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -487,7 +470,8 @@ export default {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
Status,
|
Status,
|
||||||
SearchView,
|
SearchView,
|
||||||
ResourceIcon
|
ResourceIcon,
|
||||||
|
InfiniteScrollSelect
|
||||||
},
|
},
|
||||||
name: 'ManageVolumes',
|
name: 'ManageVolumes',
|
||||||
data () {
|
data () {
|
||||||
|
|
@ -607,7 +591,6 @@ export default {
|
||||||
this.page.managed = parseInt(this.$route.query.managedpage || 1)
|
this.page.managed = parseInt(this.$route.query.managedpage || 1)
|
||||||
this.initForm()
|
this.initForm()
|
||||||
this.fetchData()
|
this.fetchData()
|
||||||
this.fetchDomains()
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isPageAllowed () {
|
isPageAllowed () {
|
||||||
|
|
@ -629,6 +612,36 @@ export default {
|
||||||
showCluster () {
|
showCluster () {
|
||||||
return this.poolscope !== 'zone'
|
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 () {
|
showHost () {
|
||||||
return this.poolscope === 'host'
|
return this.poolscope === 'host'
|
||||||
},
|
},
|
||||||
|
|
@ -970,53 +983,6 @@ export default {
|
||||||
this.updateQuery('scope', value)
|
this.updateQuery('scope', value)
|
||||||
this.fetchOptions(this.params.zones, 'zones', 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 () {
|
changeAccountType () {
|
||||||
this.importForm.selectedDomain = null
|
this.importForm.selectedDomain = null
|
||||||
this.importForm.selectedAccount = null
|
this.importForm.selectedAccount = null
|
||||||
|
|
@ -1029,8 +995,7 @@ export default {
|
||||||
this.importForm.selectedProject = null
|
this.importForm.selectedProject = null
|
||||||
this.importForm.selectedDiskoffering = null
|
this.importForm.selectedDiskoffering = null
|
||||||
this.diskOfferings = {}
|
this.diskOfferings = {}
|
||||||
this.fetchAccounts()
|
// InfiniteScrollSelect will auto-reload when apiParams changes
|
||||||
this.fetchProjects()
|
|
||||||
},
|
},
|
||||||
changeAccount () {
|
changeAccount () {
|
||||||
this.importForm.selectedProject = null
|
this.importForm.selectedProject = null
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue