| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| <template> | |
| <a-spin :spinning="formLoading"> | |
| <a-form | |
| :ref="formRef" | |
| :model="form" | |
| :rules="rules" | |
| @finish="handleSubmit" | |
| layout="vertical" | |
| v-ctrl-enter="handleSubmit" | |
| > | |
| <div v-for="(item, index) in dataResource" :key="index"> | |
| <a-form-item | |
| v-if="item.resourcetypename !== 'project' || !$route.path.startsWith('/project')" | |
| :v-bind="item.resourcetypename" | |
| :label="$t('label.max' + (item.resourcetypename ? item.resourcetypename.replace('_', '') : '')) + (item.tag ? ' [' + item.tag + ']': '')" | |
| :name="item.key" | |
| :ref="item.key"> | |
| <a-input-number | |
| :disabled="!('updateResourceLimit' in $store.getters.apis)" | |
| style="width: 100%;" | |
| v-model:value="form[item.key]" | |
| v-focus="index === 0" | |
| /> | |
| </a-form-item> | |
| <a-collapse | |
| v-if="item.taggedresource && item.taggedresource.length > 0" | |
| class="tagged-limit-collapse" | |
| @change="handleCollapseChange(item.resourcetypename)"> | |
| <a-collapse-panel key="1" :header="collapseActive[item.resourcetypename] ? $t('label.tagged.limits') : $t('label.tagged.limits') + ' - ' + item.tagsasstring"> | |
| <div v-for="(subItem, subItemIndex) in item.taggedresource" :key="subItemIndex"> | |
| <a-form-item | |
| :v-bind="subItem.resourcetypename" | |
| :label="$t('label.max') + ' #' + subItem.tag" | |
| :name="subItem.key" | |
| :ref="subItem.key"> | |
| <a-input-number | |
| :disabled="!('updateResourceLimit' in $store.getters.apis)" | |
| style="width: 100%;" | |
| v-model:value="form[subItem.key]" | |
| /> | |
| </a-form-item> | |
| </div> | |
| </a-collapse-panel> | |
| </a-collapse> | |
| </div> | |
| <div class="card-footer"> | |
| <a-button | |
| :disabled="!('updateResourceLimit' in $store.getters.apis)" | |
| v-if="!($route.meta.name === 'domain' && resource.level === 0)" | |
| :loading="formLoading" | |
| type="primary" | |
| ref="submit" | |
| @click="handleSubmit">{{ $t('label.submit') }}</a-button> | |
| </div> | |
| </a-form> | |
| </a-spin> | |
| </template> | |
| <script> | |
| import { ref, reactive, toRaw } from 'vue' | |
| import { getAPI, postAPI } from '@/api' | |
| import _ from 'lodash' | |
| export default { | |
| name: 'ResourceLimitTab', | |
| props: { | |
| resource: { | |
| type: Object, | |
| required: true | |
| }, | |
| loading: { | |
| type: Boolean, | |
| default: false | |
| } | |
| }, | |
| data () { | |
| return { | |
| formLoading: false, | |
| dataResource: [], | |
| collapseActive: {}, | |
| resourceTypeIdNames: {} | |
| } | |
| }, | |
| created () { | |
| this.formRef = ref() | |
| this.form = reactive({}) | |
| this.rules = reactive({}) | |
| this.dataResource = this.resource | |
| this.origValues = [] | |
| this.fetchData() | |
| }, | |
| watch: { | |
| resource: { | |
| handler (newData) { | |
| if (!newData || !newData.id) { | |
| return | |
| } | |
| this.fetchData() | |
| } | |
| } | |
| }, | |
| methods: { | |
| getParams () { | |
| const params = {} | |
| if (this.$route.meta.name === 'account') { | |
| params.account = this.resource.name | |
| params.domainid = this.resource.domainid | |
| } else if (this.$route.meta.name === 'domain') { | |
| params.domainid = this.resource.id | |
| } else { // project | |
| params.projectid = this.resource.id | |
| } | |
| return params | |
| }, | |
| async fetchData () { | |
| const params = this.getParams() | |
| const form = reactive({}) | |
| try { | |
| this.formLoading = true | |
| this.dataResource = await this.listResourceLimits(params) | |
| this.dataResource.forEach(item => { | |
| this.resourceTypeIdNames[item.resourcetype] = item.resourcetypename | |
| item.key = item.tag ? (item.resourcetype + '-' + item.tag) : item.resourcetype | |
| this.origValues[item.key] = form[item.key] = item.max || -1 | |
| item.taggedresource.forEach(subItem => { | |
| subItem.key = subItem.tag ? (subItem.resourcetype + '-' + subItem.tag) : subItem.resourcetype | |
| form[subItem.key] = subItem.max || -1 | |
| }) | |
| form[item.resourcetype] = item.max == null ? -1 : item.max | |
| }) | |
| this.form = form | |
| this.formRef.value.resetFields() | |
| this.formLoading = false | |
| } catch (e) { | |
| this.$notification.error({ | |
| message: this.$t('message.request.failed'), | |
| description: e | |
| }) | |
| this.formLoading = false | |
| } | |
| }, | |
| handleSubmit (e) { | |
| e.preventDefault() | |
| if (this.formLoading) return | |
| if (!this.validateTaggedLimitsForUntaggedLimits(toRaw(this.form))) { | |
| return | |
| } | |
| this.formRef.value.validate().then(() => { | |
| const values = toRaw(this.form) | |
| const arrAsync = [] | |
| const params = this.getParams() | |
| for (const key in values) { | |
| const input = values[key] | |
| if (input === this.origValues[key]) { | |
| continue | |
| } | |
| if (input === undefined) { | |
| continue | |
| } | |
| params.resourcetype = key | |
| if (key.includes('-')) { | |
| const idx = key.indexOf('-') | |
| params.resourcetype = key.substring(0, idx) | |
| params.tag = key.substring(idx + 1) | |
| } | |
| params.max = input | |
| arrAsync.push(this.updateResourceLimit(params)) | |
| } | |
| this.formLoading = true | |
| Promise.all(arrAsync).then(() => { | |
| this.$message.success(this.$t('message.apply.success')) | |
| this.fetchData() | |
| }).catch(error => { | |
| this.$notifyError(error) | |
| }).finally(() => { | |
| this.formLoading = false | |
| }) | |
| }).catch(error => { | |
| this.formRef.value.scrollToField(error.errorFields[0].name) | |
| }) | |
| }, | |
| validateTaggedLimitsForUntaggedLimits (values) { | |
| for (const key in values) { | |
| const input = values[key] | |
| if (input === undefined) { | |
| continue | |
| } | |
| if (key.includes('-')) { | |
| const idx = key.indexOf('-') | |
| const resourcetype = key.substring(0, idx) | |
| const tag = key.substring(idx + 1) | |
| const untaggedInput = values[resourcetype] | |
| if (untaggedInput > 0 && untaggedInput < input) { | |
| var err = this.$t('message.update.resource.limit.max.untagged.error').replace('%x', this.$t('label.max' + this.resourceTypeIdNames[resourcetype].replace('_', ''))) | |
| err = err.replace('%y', untaggedInput).replace('%z', tag) | |
| this.$notification.error({ | |
| message: this.$t('message.request.failed'), | |
| description: err | |
| }) | |
| return false | |
| } | |
| } | |
| } | |
| return true | |
| }, | |
| listResourceLimits (params) { | |
| return new Promise((resolve, reject) => { | |
| let dataResource = [] | |
| getAPI('listResourceLimits', params).then(json => { | |
| if (json.listresourcelimitsresponse.resourcelimit) { | |
| dataResource = json.listresourcelimitsresponse.resourcelimit | |
| dataResource.sort((a, b) => a.resourcetype - b.resourcetype) | |
| var taggedResource = dataResource?.filter(x => x.tag !== null && x.tag !== undefined) || [] | |
| dataResource = dataResource?.filter(x => x.tag === null || x.tag === undefined) || [] | |
| for (var untaggedResource of dataResource) { | |
| var tagged = taggedResource.filter(x => x.resourcetype === untaggedResource.resourcetype) || [] | |
| tagged.sort((a, b) => a.tag.localeCompare(b.tag)) | |
| untaggedResource.taggedresource = tagged | |
| var tags = _.map(tagged, 'tag') | |
| untaggedResource.tags = tags | |
| untaggedResource.tagsasstring = '#' + tags.join(', #') | |
| } | |
| } | |
| resolve(dataResource) | |
| }).catch(error => { | |
| reject(error.response.headers['x-description']) | |
| }) | |
| }) | |
| }, | |
| updateResourceLimit (params) { | |
| return new Promise((resolve, reject) => { | |
| postAPI('updateResourceLimit', params).then(json => { | |
| resolve() | |
| }).catch(error => { | |
| reject(error) | |
| }) | |
| }) | |
| }, | |
| handleCollapseChange (type) { | |
| if (this.collapseActive[type]) { | |
| this.collapseActive[type] = null | |
| return | |
| } | |
| this.collapseActive[type] = true | |
| } | |
| } | |
| } | |
| </script> | |
| <style lang="less" scoped> | |
| .card-footer { | |
| button + button { | |
| margin-left: 8px; | |
| } | |
| } | |
| .tagged-limit-collapse { | |
| margin-top: 10px; | |
| margin-bottom: 20px; | |
| } | |
| </style> |