blob: 90ed4c0aafa88278a6c7d37b66482f613207ba52 [file] [log] [blame]
// 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>
<div>
<div @keyup.ctrl.enter="handleOpenAddVMModal">
<div class="form">
<div class="form__item" ref="newRuleName">
<div class="form__label"><span class="form__required">*</span>{{ $t('label.name') }}</div>
<a-input v-focus="true" v-model:value="newRule.name"></a-input>
<span class="error-text">{{ $t('label.required') }}</span>
</div>
<div class="form__item" ref="newRulePublicPort">
<div class="form__label"><span class="form__required">*</span>{{ $t('label.publicport') }}</div>
<a-input v-model:value="newRule.publicport"></a-input>
<span class="error-text">{{ $t('label.required') }}</span>
</div>
<div class="form__item" ref="newRulePrivatePort">
<div class="form__label"><span class="form__required">*</span>{{ $t('label.privateport') }}</div>
<a-input v-model:value="newRule.privateport"></a-input>
<span class="error-text">{{ $t('label.required') }}</span>
</div>
</div>
<div class="form">
<div class="form__item" ref="newCidrList">
<tooltip-label :title="$t('label.cidrlist')" bold :tooltip="createLoadBalancerRuleParams.cidrlist.description" :tooltip-placement="'right'"/>
<a-input v-model:value="newRule.cidrlist"></a-input>
</div>
<div class="form__item">
<div class="form__label">{{ $t('label.algorithm') }}</div>
<a-select
v-model:value="newRule.algorithm"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option value="roundrobin">{{ $t('label.lb.algorithm.roundrobin') }}</a-select-option>
<a-select-option value="leastconn">{{ $t('label.lb.algorithm.leastconn') }}</a-select-option>
<a-select-option value="source">{{ $t('label.lb.algorithm.source') }}</a-select-option>
</a-select>
</div>
<div class="form__item">
<div class="form__label">{{ $t('label.protocol') }}</div>
<a-select
v-model:value="newRule.protocol"
style="min-width: 100px"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option value="tcp-proxy">{{ $t('label.tcp.proxy') }}</a-select-option>
<a-select-option value="tcp">{{ $t('label.tcp') }}</a-select-option>
<a-select-option value="udp">{{ $t('label.udp') }}</a-select-option>
</a-select>
</div>
<div class="form__item">
<div class="form__label">{{ $t('label.autoscale') }}</div>
<a-select
v-model:value="newRule.autoscale"
defaultValue="no"
style="min-width: 100px"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option value="yes">{{ $t('label.yes') }}</a-select-option>
<a-select-option value="no">{{ $t('label.no') }}</a-select-option>
</a-select>
</div>
<div class="form__item" v-if="!newRule.autoscale || newRule.autoscale === 'no' || ('vpcid' in this.resource && !('associatednetworkid' in this.resource))">
<div class="form__label" style="white-space: nowrap;">{{ $t('label.add.vms') }}</div>
<a-button :disabled="!('createLoadBalancerRule' in $store.getters.apis)" type="primary" @click="handleOpenAddVMModal">
{{ $t('label.add') }}
</a-button>
</div>
<div class="form__item" v-else-if="newRule.autoscale === 'yes'">
<div class="form__label" style="white-space: nowrap;">{{ $t('label.add') }}</div>
<a-button :disabled="!('createLoadBalancerRule' in $store.getters.apis)" type="primary" @click="handleAddNewRule">
{{ $t('label.add') }}
</a-button>
</div>
</div>
</div>
<a-divider />
<a-button
v-if="(('deleteLoadBalancerRule' in $store.getters.apis) && this.selectedItems.length > 0)"
type="primary"
danger
style="width: 100%; margin-bottom: 15px"
@click="bulkActionConfirmation()">
<template #icon><delete-outlined /></template>
{{ $t('label.action.bulk.delete.load.balancer.rules') }}
</a-button>
<a-table
size="small"
class="list-view"
:loading="loading"
:columns="columns"
:dataSource="lbRules"
:pagination="false"
:rowSelection="{selectedRowKeys: selectedRowKeys, onChange: onSelectChange}"
:rowKey="record => record.id">
<template #cidrlist="{ record }">
<span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(" ", "\n") }}</span>
</template>
<template #algorithm="{ record }">
{{ returnAlgorithmName(record.algorithm) }}
</template>
<template #protocol="{record}">
{{ getCapitalise(record.protocol) }}
</template>
<template #stickiness="{record}">
<a-button @click="() => openStickinessModal(record.id)">
{{ returnStickinessLabel(record.id) }}
</a-button>
</template>
<template #autoscale="{record}">
<div>
<router-link :to="{ path: '/autoscalevmgroup/' + record.autoscalevmgroup.id }" v-if='record.autoscalevmgroup'>
<a-button>{{ $t('label.view') }}</a-button>
</router-link>
<router-link :to="{ path: '/action/createAutoScaleVmGroup', query: { networkid: record.networkid, lbruleid : record.id } }" v-else-if='!record.ruleInstances'>
<a-button>{{ $t('label.new') }}</a-button>
</router-link>
</div>
</template>
<template #healthmonitor="{ record }">
<a-button @click="() => openHealthMonitorModal(record.id)">
{{ returnHealthMonitorLabel(record.id) }}
</a-button>
</template>
<template #add="{record}">
<a-button type="primary" @click="() => { selectedRule = record; handleOpenAddVMModal() }" v-if='!record.autoscalevmgroup'>
<template #icon>
<plus-outlined />
</template>
{{ $t('label.add') }}
</a-button>
</template>
<template #expandedRowRender="{ record }">
<div class="rule-instance-list">
<div v-for="instance in record.ruleInstances" :key="instance.loadbalancerruleinstance.id">
<div v-for="ip in instance.lbvmipaddresses" :key="ip" class="rule-instance-list__item">
<div>
<status :text="instance.loadbalancerruleinstance.state" />
<desktop-outlined />
<router-link :to="{ path: '/vm/' + instance.loadbalancerruleinstance.id }">
{{ instance.loadbalancerruleinstance.displayname }}
</router-link>
</div>
<div>{{ ip }}</div>
<tooltip-button
:disabled='record.autoscalevmgroup'
:tooltip="$t('label.remove.vm.from.lb')"
type="primary"
:danger="true"
icon="delete-outlined"
@onClick="() => handleDeleteInstanceFromRule(instance, record, ip)" />
</div>
</div>
</div>
</template>
<template #actions="{record}">
<div class="actions">
<tooltip-button :tooltip="$t('label.edit')" icon="edit-outlined" @onClick="() => openEditRuleModal(record)" />
<tooltip-button :tooltip="$t('label.edit.tags')" :disabled="!('updateLoadBalancerRule' in $store.getters.apis)" icon="tag-outlined" @onClick="() => openTagsModal(record.id)" />
<a-popconfirm
:title="$t('label.delete') + '?'"
@confirm="handleDeleteRule(record)"
:okText="$t('label.yes')"
:cancelText="$t('label.no')"
>
<tooltip-button
:tooltip="$t('label.delete')"
:disabled="!('deleteLoadBalancerRule' in $store.getters.apis) || record.autoscalevmgroup"
type="primary"
:danger="true"
icon="delete-outlined" />
</a-popconfirm>
</div>
</template>
</a-table>
<a-pagination
class="pagination"
size="small"
:current="page"
:pageSize="pageSize"
:total="totalCount"
:showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
:pageSizeOptions="['10', '20', '40', '80', '100']"
@change="handleChangePage"
@showSizeChange="handleChangePageSize"
showSizeChanger>
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
<a-modal
v-if="tagsModalVisible"
:title="$t('label.edit.tags')"
:visible="tagsModalVisible"
:footer="null"
:closable="true"
:afterClose="closeModal"
:maskClosable="false"
class="tags-modal"
@cancel="tagsModalVisible = false">
<span v-show="tagsModalLoading" class="modal-loading">
<loading-outlined />
</span>
<a-form
:ref="formRef"
:model="form"
:rules="rules"
class="add-tags"
@finish="handleAddTag"
v-ctrl-enter="handleAddTag"
>
<div class="add-tags__input">
<p class="add-tags__label">{{ $t('label.key') }}</p>
<a-form-item ref="key" name="key">
<a-input
v-focus="true"
v-model:value="form.key" />
</a-form-item>
</div>
<div class="add-tags__input">
<p class="add-tags__label">{{ $t('label.value') }}</p>
<a-form-item ref="value" name="value">
<a-input v-model:value="form.value" />
</a-form-item>
</div>
<a-button :disabled="!('createTags' in $store.getters.apis)" type="primary" ref="submit" @click="handleAddTag">{{ $t('label.add') }}</a-button>
</a-form>
<a-divider />
<div v-show="!tagsModalLoading" class="tags-container">
<div class="tags" v-for="(tag, index) in tags" :key="index">
<a-tag :key="index" :closable="'deleteTags' in $store.getters.apis" @close="() => handleDeleteTag(tag)">
{{ tag.key }} = {{ tag.value }}
</a-tag>
</div>
</div>
<a-button class="add-tags-done" @click="tagsModalVisible = false" type="primary">{{ $t('label.done') }}</a-button>
</a-modal>
<a-modal
:title="$t('label.configure.sticky.policy')"
:visible="stickinessModalVisible"
:footer="null"
:afterClose="closeModal"
:maskClosable="false"
:closable="true"
:okButtonProps="{ props: {htmlType: 'submit'}}"
@cancel="stickinessModalVisible = false">
<span v-show="stickinessModalLoading" class="modal-loading">
<loading-outlined />
</span>
<a-form
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmitStickinessForm"
v-ctrl-enter="handleSubmitStickinessForm"
class="custom-ant-form"
>
<a-form-item name="methodname" ref="methodname" :label="$t('label.stickiness.method')">
<a-select
v-focus="true"
v-model:value="form.methodname"
@change="handleStickinessMethodSelectChange"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option value="LbCookie">{{ $t('label.lb.cookie') }}</a-select-option>
<a-select-option value="AppCookie">{{ $t('label.app.cookie') }}</a-select-option>
<a-select-option value="SourceBased">{{ $t('label.source.based') }}</a-select-option>
<a-select-option value="none">{{ $t('label.none') }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item
name="name"
ref="name"
:label="$t('label.sticky.name')"
v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
'AppCookie' || stickinessPolicyMethod === 'SourceBased'">
<a-input v-model:value="form.name" />
</a-form-item>
<a-form-item
name="cookieName"
ref="cookieName"
:label="$t('label.sticky.cookie-name')"
v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
'AppCookie'">
<a-input v-model:value="form.cookieName" />
</a-form-item>
<a-form-item
name="mode"
ref="mode"
:label="$t('label.sticky.mode')"
v-show="stickinessPolicyMethod === 'LbCookie' || stickinessPolicyMethod ===
'AppCookie'">
<a-input v-model:value="form.mode" />
</a-form-item>
<a-form-item name="nocache" ref="nocache" :label="$t('label.sticky.nocache')" v-show="stickinessPolicyMethod === 'LbCookie'">
<a-checkbox v-model:checked="form.nocache"></a-checkbox>
</a-form-item>
<a-form-item name="indirect" ref="indirect" :label="$t('label.sticky.indirect')" v-show="stickinessPolicyMethod === 'LbCookie'">
<a-checkbox v-model:checked="form.indirect"></a-checkbox>
</a-form-item>
<a-form-item name="postonly" ref="postonly" :label="$t('label.sticky.postonly')" v-show="stickinessPolicyMethod === 'LbCookie'">
<a-checkbox v-model:checked="form.postonly"></a-checkbox>
</a-form-item>
<a-form-item name="domain" ref="domain" :label="$t('label.domain')" v-show="stickinessPolicyMethod === 'LbCookie'">
<a-input v-model:value="form.domain" />
</a-form-item>
<a-form-item name="length" ref="length" :label="$t('label.sticky.length')" v-show="stickinessPolicyMethod === 'AppCookie'">
<a-input v-model:value="form.length" type="number" />
</a-form-item>
<a-form-item name="holdtime" ref="holdtime" :label="$t('label.sticky.holdtime')" v-show="stickinessPolicyMethod === 'AppCookie'">
<a-input v-model:value="form.holdtime" type="number" />
</a-form-item>
<a-form-item name="requestLearn" ref="requestLearn" :label="$t('label.sticky.request-learn')" v-show="stickinessPolicyMethod === 'AppCookie'">
<a-checkbox v-model:checked="form.requestLearn"></a-checkbox>
</a-form-item>
<a-form-item name="prefix" ref="prefix" :label="$t('label.sticky.prefix')" v-show="stickinessPolicyMethod === 'AppCookie'">
<a-checkbox v-model:checked="form.prefix"></a-checkbox>
</a-form-item>
<a-form-item name="tablesize" ref="tablesize" :label="$t('label.sticky.tablesize')" v-show="stickinessPolicyMethod === 'SourceBased'">
<a-input v-model:value="form.tablesize" />
</a-form-item>
<a-form-item name="expire" ref="expire" :label="$t('label.sticky.expire')" v-show="stickinessPolicyMethod === 'SourceBased'">
<a-input v-model:value="form.expire" />
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="stickinessModalVisible = false">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleSubmitStickinessForm">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
<a-modal
:title="$t('label.edit.rule')"
:visible="editRuleModalVisible"
:afterClose="closeModal"
:maskClosable="false"
:closable="true"
:footer="null"
@cancel="editRuleModalVisible = false">
<span v-show="editRuleModalLoading" class="modal-loading">
<loading-outlined />
</span>
<div class="edit-rule" v-if="selectedRule" v-ctrl-enter="handleSubmitEditForm">
<div class="edit-rule__item">
<p class="edit-rule__label">{{ $t('label.name') }}</p>
<a-input v-focus="true" v-model:value="editRuleDetails.name" />
</div>
<div class="edit-rule__item">
<p class="edit-rule__label">{{ $t('label.algorithm') }}</p>
<a-select
v-model:value="editRuleDetails.algorithm"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option value="roundrobin">{{ $t('label.lb.algorithm.roundrobin') }}</a-select-option>
<a-select-option value="leastconn">{{ $t('label.lb.algorithm.leastconn') }}</a-select-option>
<a-select-option value="source">{{ $t('label.lb.algorithm.source') }}</a-select-option>
</a-select>
</div>
<div class="edit-rule__item">
<p class="edit-rule__label">{{ $t('label.protocol') }}</p>
<a-select
v-model:value="editRuleDetails.protocol"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option value="tcp-proxy">{{ $t('label.tcp.proxy') }}</a-select-option>
<a-select-option value="tcp">{{ $t('label.tcp') }}</a-select-option>
<a-select-option value="udp">{{ $t('label.udp') }}</a-select-option>
</a-select>
</div>
<div :span="24" class="action-button">
<a-button @click="() => editRuleModalVisible = false">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" @click="handleSubmitEditForm">{{ $t('label.ok') }}</a-button>
</div>
</div>
</a-modal>
<a-modal
:title="$t('label.add.vms')"
:maskClosable="false"
:closable="true"
v-if="addVmModalVisible"
:visible="addVmModalVisible"
class="vm-modal"
width="60vw"
:footer="null"
@cancel="closeModal"
>
<div @keyup.ctrl.enter="handleAddNewRule">
<span
v-if="'vpcid' in resource && !('associatednetworkid' in resource)">
<strong>{{ $t('label.select.tier') }} </strong>
<a-select
v-focus="'vpcid' in resource && !('associatednetworkid' in resource)"
v-model:value="selectedTier"
@change="fetchVirtualMachines()"
:placeholder="$t('label.select.tier')"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="tier in tiers.data"
:loading="tiers.loading"
:key="tier.id">
{{ tier.displaytext }}
</a-select-option>
</a-select>
</span>
<a-input-search
v-focus="!('vpcid' in resource && !('associatednetworkid' in resource))"
class="input-search"
:placeholder="$t('label.search')"
v-model:value="searchQuery"
allowClear
@search="onSearch" />
<a-table
size="small"
class="list-view"
:loading="addVmModalLoading"
:columns="vmColumns"
:dataSource="vms"
:pagination="false"
:rowKey="record => record.id"
:scroll="{ y: 300 }">
<template #name="{text, record, index}">
<span>
{{ text }}
</span>
<loading-outlined v-if="addVmModalNicLoading" />
<a-select
style="display: block"
v-else-if="!addVmModalNicLoading && newRule.virtualmachineid[index] === record.id"
mode="multiple"
v-model:value="newRule.vmguestip[index]"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="(nic, nicIndex) in nics[index]" :key="nic" :value="nic">
{{ nic }}{{ nicIndex === 0 ? ` (${$t('label.primary')})` : null }}
</a-select-option>
</a-select>
</template>
<template #state="{text}">
<status :text="text ? text : ''" displayText></status>
</template>
<template #action="{text, record, index}" style="text-align: center" :text="text">
<a-checkbox v-model:value="record.id" @change="e => fetchNics(e, index)" :disabled="newRule.autoscale" />
</template>
</a-table>
<a-pagination
class="pagination"
size="small"
:current="vmPage"
:pageSize="vmPageSize"
:total="vmCount"
:showTotal="total => `${$t('label.total')} ${total} ${$t('label.items')}`"
:pageSizeOptions="['10', '20', '40', '80', '100']"
@change="handleChangeVmPage"
@showSizeChange="handleChangeVmPageSize"
showSizeChanger>
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button :disabled="newRule.virtualmachineid === []" type="primary" ref="submit" @click="handleAddNewRule">{{ $t('label.ok') }}</a-button>
</div>
</div>
</a-modal>
<a-modal
v-if="healthMonitorModal"
:title="$t('label.configure.health.monitor')"
:visible="healthMonitorModal"
:footer="null"
:maskClosable="false"
:closable="true"
@cancel="closeMonitorModal">
<a-form
:ref="monitorRef"
:model="monitorForm"
:rules="monitorRules"
layout="vertical"
@finish="handleConfigHealthMonitor"
v-ctrl-enter="handleConfigHealthMonitor">
<a-form-item name="type" ref="type" :label="$t('label.monitor.type')">
<a-select
v-focus="true"
v-model:value="monitorForm.type"
@change="(value) => { healthMonitorParams.type = value }"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option value="PING">PING</a-select-option>
<a-select-option value="TCP">TCP</a-select-option>
<a-select-option value="HTTP">HTTP</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="retry" ref="retry" :label="$t('label.monitor.retry')">
<a-input v-model:value="monitorForm.retry" />
</a-form-item>
<a-form-item name="timeout" ref="timeout" :label="$t('label.monitor.timeout')">
<a-input v-model:value="monitorForm.timeout" />
</a-form-item>
<a-form-item name="interval" ref="interval" :label="$t('label.monitor.interval')">
<a-input v-model:value="monitorForm.interval" />
</a-form-item>
<a-form-item
name="httpmethodtype"
ref="httpmethodtype"
:label="$t('label.monitor.http.method')"
v-if="healthMonitorParams.type === 'HTTP'">
<a-select
v-focus="true"
v-model:value="monitorForm.httpmethodtype"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option value="GET">GET</a-select-option>
<a-select-option value="HEAD">HEAD</a-select-option>
</a-select>
</a-form-item>
<a-form-item
name="expectedcode"
ref="expectedcode"
:label="$t('label.monitor.expected.code')"
v-if="healthMonitorParams.type === 'HTTP'">
<a-input v-model:value="monitorForm.expectedcode" />
</a-form-item>
<a-form-item
name="urlpath"
ref="urlpath"
:label="$t('label.monitor.url')"
v-if="healthMonitorParams.type === 'HTTP'">
<a-input v-model:value="monitorForm.urlpath" />
</a-form-item>
<div :span="24" class="action-button">
<a-button :loading="healthMonitorLoading" @click="closeMonitorModal">{{ $t('label.cancel') }}</a-button>
<a-button :loading="healthMonitorLoading" type="primary" @click="handleConfigHealthMonitor">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
<bulk-action-view
v-if="showConfirmationAction || showGroupActionModal"
:showConfirmationAction="showConfirmationAction"
:showGroupActionModal="showGroupActionModal"
:items="lbRules"
:selectedRowKeys="selectedRowKeys"
:selectedItems="selectedItems"
:columns="columns"
:selectedColumns="selectedColumns"
:filterColumns="filterColumns"
action="deleteLoadBalancerRule"
:loading="loading"
:message="message"
@group-action="deleteRules"
@handle-cancel="handleCancel"
@close-modal="closeModal" />
</div>
</template>
<script>
import { ref, reactive, toRaw, nextTick } from 'vue'
import { api } from '@/api'
import { mixinForm } from '@/utils/mixin'
import Status from '@/components/widgets/Status'
import TooltipButton from '@/components/widgets/TooltipButton'
import BulkActionView from '@/components/view/BulkActionView'
import eventBus from '@/config/eventBus'
import TooltipLabel from '@/components/widgets/TooltipLabel'
export default {
name: 'LoadBalancing',
mixins: [mixinForm],
components: {
Status,
TooltipButton,
BulkActionView,
TooltipLabel
},
props: {
resource: {
type: Object,
required: true
}
},
inject: ['parentFetchData', 'parentToggleLoading'],
data () {
return {
selectedRowKeys: [],
showGroupActionModal: false,
selectedItems: [],
selectedColumns: [],
filterColumns: ['State', 'Action', 'Add VMs', 'Stickiness'],
showConfirmationAction: false,
message: {
title: this.$t('label.action.bulk.delete.load.balancer.rules'),
confirmMessage: this.$t('label.confirm.delete.loadbalancer.rules')
},
loading: true,
lbRules: [],
tagsModalVisible: false,
tagsModalLoading: false,
tags: [],
selectedRule: null,
selectedTier: null,
stickinessModalVisible: false,
stickinessPolicies: [],
stickinessModalLoading: false,
selectedStickinessPolicy: null,
stickinessPolicyMethod: 'LbCookie',
editRuleModalVisible: false,
editRuleModalLoading: false,
editRuleDetails: {
name: '',
algorithm: '',
protocol: ''
},
newRule: {
algorithm: 'roundrobin',
name: '',
privateport: '',
publicport: '',
protocol: 'tcp',
virtualmachineid: [],
vmguestip: [],
cidrlist: ''
},
addVmModalVisible: false,
addVmModalLoading: false,
addVmModalNicLoading: false,
vms: [],
nics: [],
totalCount: 0,
page: 1,
pageSize: 10,
columns: [
{
title: this.$t('label.name'),
dataIndex: 'name'
},
{
title: this.$t('label.publicport'),
dataIndex: 'publicport'
},
{
title: this.$t('label.privateport'),
dataIndex: 'privateport'
},
{
title: this.$t('label.cidrlist'),
slots: { customRender: 'cidrlist' }
},
{
title: this.$t('label.algorithm'),
slots: { customRender: 'algorithm' }
},
{
title: this.$t('label.protocol'),
slots: { customRender: 'protocol' }
},
{
title: this.$t('label.state'),
dataIndex: 'state'
},
{
title: this.$t('label.action.configure.stickiness'),
slots: { customRender: 'stickiness' }
},
{
title: this.$t('label.autoscale'),
slots: { customRender: 'autoscale' }
},
{
title: this.$t('label.add.vms'),
slots: { customRender: 'add' }
},
{
title: this.$t('label.action'),
slots: { customRender: 'actions' }
}
],
tiers: {
loading: false,
data: []
},
vmColumns: [
{
title: this.$t('label.name'),
dataIndex: 'name',
slots: { customRender: 'name' },
width: 220
},
{
title: this.$t('label.state'),
dataIndex: 'state',
slots: { customRender: 'state' }
},
{
title: this.$t('label.displayname'),
dataIndex: 'displayname'
},
{
title: this.$t('label.account'),
dataIndex: 'account'
},
{
title: this.$t('label.zonename'),
dataIndex: 'zonename'
},
{
title: this.$t('label.select'),
dataIndex: 'action',
slots: { customRender: 'action' },
width: 80
}
],
vmPage: 1,
vmPageSize: 10,
vmCount: 0,
searchQuery: null,
tungstenHealthMonitors: [],
healthMonitorModal: false,
healthMonitorParams: {
type: 'PING',
retry: 3,
timeout: 5,
interval: 5,
httpmethodtype: 'GET',
expectedcode: undefined,
urlpath: '/'
},
healthMonitorLoading: false
}
},
computed: {
hasSelected () {
return this.selectedRowKeys.length > 0
}
},
beforeCreate () {
this.createLoadBalancerRuleParams = this.$getApiParams('createLoadBalancerRule')
},
created () {
this.initForm()
this.initMonitorForm()
this.fetchData()
},
watch: {
resource: {
deep: true,
handler (newItem) {
if (!newItem || !newItem.id) {
return
}
this.fetchData()
}
}
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({})
this.rules = reactive({})
},
initMonitorForm () {
this.monitorRef = ref()
this.monitorForm = reactive({
type: this.healthMonitorParams.type,
retry: this.healthMonitorParams.retry,
timeout: this.healthMonitorParams.timeout,
interval: this.healthMonitorParams.interval,
httpmethodtype: this.healthMonitorParams.httpmethodtype,
expectedcode: this.healthMonitorParams.expectedcode,
urlpath: this.healthMonitorParams.urlpath
})
this.monitorRules = reactive({
retry: [{ required: true, message: this.$t('message.error.required.input') }],
timeout: [{ required: true, message: this.$t('message.error.required.input') }],
interval: [{ required: true, message: this.$t('message.error.required.input') }],
expectedcode: [{ required: true, message: this.$t('message.error.required.input') }],
urlpath: [{ required: true, message: this.$t('message.error.required.input') }]
})
},
fetchData () {
this.fetchListTiers()
this.fetchLBRules()
},
fetchListTiers () {
this.tiers.loading = true
api('listNetworks', {
account: this.resource.account,
domainid: this.resource.domainid,
supportedservices: 'Lb',
vpcid: this.resource.vpcid
}).then(json => {
this.tiers.data = json.listnetworksresponse.network || []
this.selectedTier = this.tiers.data?.[0]?.id ? this.tiers.data[0].id : null
if (this.tiers.data?.[0]?.broadcasturi === 'tf://tf') {
this.columns.splice(8, 0, {
title: this.$t('label.action.health.monitor'),
slots: { customRender: 'healthmonitor' }
})
}
}).catch(error => {
this.$notifyError(error)
}).finally(() => { this.tiers.loading = false })
},
fetchLBRules () {
this.loading = true
this.lbRules = []
this.stickinessPolicies = []
api('listLoadBalancerRules', {
listAll: true,
publicipid: this.resource.id,
page: this.page,
pageSize: this.pageSize
}).then(response => {
this.lbRules = response.listloadbalancerrulesresponse.loadbalancerrule || []
this.totalCount = response.listloadbalancerrulesresponse.count || 0
}).then(() => {
if (this.lbRules.length > 0) {
setTimeout(() => {
this.fetchLBRuleInstances()
}, 100)
this.fetchLBStickinessPolicies()
this.fetchLBTungstenFabricHealthMonitor()
this.fetchAutoScaleVMgroups()
return
}
this.loading = false
}).catch(error => {
this.$notifyError(error)
this.loading = false
})
},
fetchLBRuleInstances () {
for (const rule of this.lbRules) {
this.loading = true
api('listLoadBalancerRuleInstances', {
listAll: true,
lbvmips: true,
id: rule.id
}).then(response => {
rule.ruleInstances = response.listloadbalancerruleinstancesresponse.lbrulevmidip
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}
},
fetchLBStickinessPolicies () {
this.loading = true
this.lbRules.forEach(rule => {
api('listLBStickinessPolicies', {
listAll: true,
lbruleid: rule.id
}).then(response => {
this.stickinessPolicies.push(...response.listlbstickinesspoliciesresponse.stickinesspolicies)
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
})
},
fetchAutoScaleVMgroups () {
this.loading = true
this.lbRules.forEach(rule => {
api('listAutoScaleVmGroups', {
listAll: true,
lbruleid: rule.id
}).then(response => {
rule.autoscalevmgroup = response.listautoscalevmgroupsresponse?.autoscalevmgroup?.[0]
}).finally(() => {
this.loading = false
})
})
},
returnAlgorithmName (name) {
switch (name) {
case 'leastconn':
return 'Least connections'
case 'roundrobin' :
return 'Round-robin'
case 'source':
return 'Source'
default :
return ''
}
},
returnStickinessLabel (id) {
const match = this.stickinessPolicies.filter(policy => policy.lbruleid === id)
if (match.length > 0 && match[0].stickinesspolicy.length > 0) {
return match[0].stickinesspolicy[0].methodname
}
return 'Configure'
},
getCapitalise (val) {
if (!val) {
return
}
if (val === 'all') return this.$t('label.all')
return val.toUpperCase()
},
openTagsModal (id) {
this.initForm()
this.rules = {
key: [{ required: true, message: this.$t('message.specify.tag.key') }],
value: [{ required: true, message: this.$t('message.specify.tag.value') }]
}
this.tagsModalLoading = true
this.tagsModalVisible = true
this.tags = []
this.selectedRule = id
api('listTags', {
resourceId: id,
resourceType: 'LoadBalancer',
listAll: true
}).then(response => {
this.tags = response.listtagsresponse.tag
this.tagsModalLoading = false
}).catch(error => {
this.$notifyError(error)
this.closeModal()
})
},
handleAddTag (e) {
if (this.tagsModalLoading) return
this.tagsModalLoading = true
e.preventDefault()
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
api('createTags', {
'tags[0].key': values.key,
'tags[0].value': values.value,
resourceIds: this.selectedRule,
resourceType: 'LoadBalancer'
}).then(response => {
this.$pollJob({
jobId: response.createtagsresponse.jobid,
successMessage: this.$t('message.success.add.tag'),
successMethod: () => {
this.parentToggleLoading()
this.openTagsModal(this.selectedRule)
},
errorMessage: this.$t('message.add.tag.failed'),
errorMethod: () => {
this.parentToggleLoading()
this.closeModal()
},
loadingMessage: this.$t('message.add.tag.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
}
})
}).catch(error => {
this.$notifyError(error)
})
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
}).finally(() => {
this.tagsModalLoading = false
})
},
handleDeleteTag (tag) {
this.tagsModalLoading = true
api('deleteTags', {
'tags[0].key': tag.key,
'tags[0].value': tag.value,
resourceIds: tag.resourceid,
resourceType: 'LoadBalancer'
}).then(response => {
this.$pollJob({
jobId: response.deletetagsresponse.jobid,
successMessage: this.$t('message.success.delete.tag'),
successMethod: () => {
this.parentToggleLoading()
this.openTagsModal(this.selectedRule)
},
errorMessage: this.$t('message.delete.tag.failed'),
errorMethod: () => {
this.parentToggleLoading()
this.closeModal()
},
loadingMessage: this.$t('message.delete.tag.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
}
})
}).catch(error => {
this.$notifyError(error)
})
},
openStickinessModal (id) {
this.initForm()
this.rules = {
methodname: [{ required: true, message: this.$t('message.error.specify.stickiness.method') }]
}
this.stickinessModalVisible = true
this.selectedRule = id
const match = this.stickinessPolicies.find(policy => policy.lbruleid === id)
if (match && match.stickinesspolicy.length > 0) {
this.selectedStickinessPolicy = match.stickinesspolicy[0]
this.stickinessPolicyMethod = this.selectedStickinessPolicy.methodname
nextTick().then(() => {
this.form.methodname = this.selectedStickinessPolicy.methodname
this.form.name = this.selectedStickinessPolicy.name
this.form.cookieName = this.selectedStickinessPolicy.params['cookie-name']
this.form.mode = this.selectedStickinessPolicy.params.mode
this.form.domain = this.selectedStickinessPolicy.params.domain
this.form.length = this.selectedStickinessPolicy.params.length
this.form.holdtime = this.selectedStickinessPolicy.params.holdtime
this.form.nocache = !!this.selectedStickinessPolicy.params.nocache
this.form.indirect = !!this.selectedStickinessPolicy.params.indirect
this.form.postonly = !!this.selectedStickinessPolicy.params.postonly
this.form.requestLearn = !!this.selectedStickinessPolicy.params['request-learn']
this.form.prefix = !!this.selectedStickinessPolicy.params.prefix
})
}
},
handleAddStickinessPolicy (data, values) {
api('createLBStickinessPolicy', {
...data,
lbruleid: this.selectedRule,
name: values.name,
methodname: values.methodname
}).then(response => {
this.$pollJob({
jobId: response.createLBStickinessPolicy.jobid,
successMessage: this.$t('message.success.config.sticky.policy'),
successMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
errorMessage: this.$t('message.config.sticky.policy.failed'),
errorMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
loadingMessage: this.$t('message.config.sticky.policy.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.fetchData()
this.closeModal()
}
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.closeModal()
})
},
handleDeleteStickinessPolicy () {
this.stickinessModalLoading = true
api('deleteLBStickinessPolicy', { id: this.selectedStickinessPolicy.id }).then(response => {
this.$pollJob({
jobId: response.deleteLBstickinessrruleresponse.jobid,
successMessage: this.$t('message.success.remove.sticky.policy'),
successMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
errorMessage: this.$t('message.remove.sticky.policy.failed'),
errorMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
loadingMessage: this.$t('message.remove.sticky.policy.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.fetchData()
this.closeModal()
}
})
}).catch(error => {
this.$notifyError(error)
})
},
handleSubmitStickinessForm (e) {
if (this.stickinessModalLoading) return
this.stickinessModalLoading = true
e.preventDefault()
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
if (values.methodname === 'none') {
this.handleDeleteStickinessPolicy()
return
}
if (values.name === null || values.name === undefined || values.name === '') {
this.$notification.error({
message: this.$t('label.error'),
description: this.$t('message.error.specify.sticky.name')
})
return
}
values.nocache = this.form.nocache
values.indirect = this.form.indirect
values.postonly = this.form.postonly
values.requestLearn = this.form.requestLearn
values.prefix = this.form.prefix
let data = {}
let count = 0
Object.entries(values).forEach(([key, val]) => {
if (val && key !== 'name' && key !== 'methodname') {
if (key === 'cookieName') {
data = { ...data, ...{ [`param[${count}].name`]: 'cookie-name' } }
} else if (key === 'requestLearn') {
data = { ...data, ...{ [`param[${count}].name`]: 'request-learn' } }
} else {
data = { ...data, ...{ [`param[${count}].name`]: key } }
}
data = { ...data, ...{ [`param[${count}].value`]: val } }
count++
}
})
this.handleAddStickinessPolicy(data, values)
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
}).finally(() => {
this.stickinessModalLoading = false
})
},
handleStickinessMethodSelectChange (e) {
if (this.formRef.value) this.formRef.value.resetFields()
this.stickinessPolicyMethod = e
this.form.methodname = e
},
handleDeleteInstanceFromRule (instance, rule, ip) {
this.loading = true
api('removeFromLoadBalancerRule', {
id: rule.id,
'vmidipmap[0].vmid': instance.loadbalancerruleinstance.id,
'vmidipmap[0].vmip': ip
}).then(response => {
this.$pollJob({
jobId: response.removefromloadbalancerruleresponse.jobid,
successMessage: this.$t('message.success.remove.instance.rule'),
successMethod: () => {
this.fetchData()
},
errorMessage: this.$t('message.remove.instance.failed'),
errorMethod: () => {
this.fetchData()
},
loadingMessage: this.$t('message.remove.instance.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.fetchData()
}
})
}).catch(error => {
this.$notifyError(error)
this.fetchData()
})
},
openEditRuleModal (rule) {
this.selectedRule = rule
this.editRuleModalVisible = true
this.editRuleDetails.name = this.selectedRule.name
this.editRuleDetails.algorithm = this.selectedRule.algorithm
this.editRuleDetails.protocol = this.selectedRule.protocol
},
handleSubmitEditForm () {
if (this.editRuleModalLoading) return
this.loading = true
this.editRuleModalLoading = true
api('updateLoadBalancerRule', {
...this.editRuleDetails,
id: this.selectedRule.id
}).then(response => {
this.$pollJob({
jobId: response.updateloadbalancerruleresponse.jobid,
successMessage: this.$t('message.success.edit.rule'),
successMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
errorMessage: this.$t('message.edit.rule.failed'),
errorMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
loadingMessage: this.$t('message.edit.rule.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.fetchData()
this.closeModal()
}
})
}).catch(error => {
this.$notifyError(error)
this.loading = false
})
},
setSelection (selection) {
this.selectedRowKeys = selection
this.$emit('selection-change', this.selectedRowKeys)
this.selectedItems = (this.lbRules.filter(function (item) {
return selection.indexOf(item.id) !== -1
}))
},
resetSelection () {
this.setSelection([])
},
onSelectChange (selectedRowKeys, selectedRows) {
this.setSelection(selectedRowKeys)
},
bulkActionConfirmation () {
this.showConfirmationAction = true
this.selectedColumns = this.columns.filter(column => {
return !this.filterColumns.includes(column.title)
})
this.selectedItems = this.selectedItems.map(v => ({ ...v, status: 'InProgress' }))
},
handleCancel () {
eventBus.emit('update-bulk-job-status', { items: this.selectedItems, action: false })
this.showGroupActionModal = false
this.selectedItems = []
this.selectedColumns = []
this.selectedRowKeys = []
this.parentFetchData()
},
deleteRules (e) {
this.showConfirmationAction = false
this.selectedColumns.splice(0, 0, {
dataIndex: 'status',
title: this.$t('label.operation.status'),
slots: { customRender: 'status' },
filters: [
{ text: 'In Progress', value: 'InProgress' },
{ text: 'Success', value: 'success' },
{ text: 'Failed', value: 'failed' }
]
})
if (this.selectedRowKeys.length > 0) {
this.showGroupActionModal = true
}
for (const rule of this.selectedItems) {
this.handleDeleteRule(rule)
}
},
handleDeleteRule (rule) {
this.loading = true
api('deleteLoadBalancerRule', {
id: rule.id
}).then(response => {
const jobId = response.deleteloadbalancerruleresponse.jobid
eventBus.emit('update-job-details', { jobId, resourceId: null })
this.$pollJob({
title: this.$t('label.action.delete.load.balancer'),
description: rule.id,
jobId: jobId,
successMessage: this.$t('message.success.remove.rule'),
successMethod: () => {
if (this.selectedItems.length > 0) {
eventBus.emit('update-resource-state', { selectedItems: this.selectedItems, resource: rule.id, state: 'success' })
}
if (this.selectedRowKeys.length === 0) {
this.parentToggleLoading()
this.fetchData()
}
this.closeModal()
},
errorMessage: this.$t('message.remove.rule.failed'),
errorMethod: () => {
if (this.selectedItems.length > 0) {
eventBus.emit('update-resource-state', { selectedItems: this.selectedItems, resource: rule.id, state: 'failed' })
}
if (this.selectedRowKeys.length === 0) {
this.parentToggleLoading()
this.fetchData()
}
this.closeModal()
},
loadingMessage: this.$t('message.delete.rule.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
if (this.selectedRowKeys.length === 0) {
this.parentToggleLoading()
this.parentFetchData()
}
this.closeModal()
},
bulkAction: `${this.selectedItems.length > 0}` && this.showGroupActionModal
})
}).catch(error => {
this.$notifyError(error)
this.loading = false
})
},
checkNewRule () {
if (!this.selectedRule) {
if (!this.newRule.name) {
this.$refs.newRuleName.classList.add('error')
} else {
this.$refs.newRuleName.classList.remove('error')
}
if (!this.newRule.publicport) {
this.$refs.newRulePublicPort.classList.add('error')
} else {
this.$refs.newRulePublicPort.classList.remove('error')
}
if (!this.newRule.privateport) {
this.$refs.newRulePrivatePort.classList.add('error')
} else {
this.$refs.newRulePrivatePort.classList.remove('error')
}
if (!this.newRule.name || !this.newRule.publicport || !this.newRule.privateport) return false
}
return true
},
handleOpenAddVMModal () {
if (this.addVmModalLoading) return
if (!this.checkNewRule()) {
return
}
this.addVmModalVisible = true
this.fetchVirtualMachines()
},
fetchNics (e, index) {
if (!e.target.checked) {
this.newRule.virtualmachineid[index] = null
this.nics[index] = null
this.newRule.vmguestip[index] = null
return
}
this.newRule.virtualmachineid[index] = e.target.value
this.addVmModalNicLoading = true
api('listNics', {
virtualmachineid: e.target.value,
networkid: ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
}).then(response => {
if (!response || !response.listnicsresponse || !response.listnicsresponse.nic[0]) return
const newItem = []
newItem.push(response.listnicsresponse.nic[0].ipaddress)
if (response.listnicsresponse.nic[0].secondaryip) {
newItem.push(...response.listnicsresponse.nic[0].secondaryip.map(ip => ip.ipaddress))
}
this.nics[index] = newItem
this.newRule.vmguestip[index] = [this.nics[index][0]]
this.addVmModalNicLoading = false
}).catch(error => {
this.$notifyError(error)
this.closeModal()
})
},
fetchVirtualMachines () {
this.vmCount = 0
this.vms = []
this.addVmModalLoading = true
const networkId = ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
if (!networkId) {
this.addVmModalLoading = false
return
}
api('listVirtualMachines', {
listAll: true,
keyword: this.searchQuery,
page: this.vmPage,
pagesize: this.vmPageSize,
networkid: networkId,
account: this.resource.account,
domainid: this.resource.domainid
}).then(response => {
this.vmCount = response.listvirtualmachinesresponse.count || 0
this.vms = response.listvirtualmachinesresponse.virtualmachine || []
this.vms.forEach((vm, index) => {
this.newRule.virtualmachineid[index] = null
this.nics[index] = null
this.newRule.vmguestip[index] = null
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.addVmModalLoading = false
})
},
handleAssignToLBRule (data) {
const vmIDIpMap = {}
let selectedVmCount = 0
let count = 0
let innerCount = 0
this.newRule.vmguestip.forEach(ip => {
if (Array.isArray(ip)) {
ip.forEach(i => {
vmIDIpMap[`vmidipmap[${innerCount}].vmid`] = this.newRule.virtualmachineid[count]
vmIDIpMap[`vmidipmap[${innerCount}].vmip`] = i
innerCount++
})
} else {
vmIDIpMap[`vmidipmap[${innerCount}].vmid`] = this.newRule.virtualmachineid[count]
vmIDIpMap[`vmidipmap[${innerCount}].vmip`] = ip
innerCount++
}
if (this.newRule.virtualmachineid[count]) {
selectedVmCount++
}
count++
})
if (selectedVmCount === 0) {
this.fetchData()
return
}
this.loading = true
api('assignToLoadBalancerRule', {
id: data,
...vmIDIpMap
}).then(response => {
this.$pollJob({
jobId: response.assigntoloadbalancerruleresponse.jobid,
successMessage: this.$t('message.success.assign.vm'),
successMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
errorMessage: this.$t('message.assign.vm.failed'),
errorMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeModal()
},
loadingMessage: this.$t('message.assign.vm.processing'),
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.fetchData()
this.closeModal()
}
})
})
},
handleAddNewRule () {
if (this.loading) return
this.loading = true
if (this.selectedRule) {
this.handleAssignToLBRule(this.selectedRule.id)
return
} else if (!this.checkNewRule()) {
this.loading = false
return
}
const networkId = ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
api('createLoadBalancerRule', {
openfirewall: false,
networkid: networkId,
publicipid: this.resource.id,
algorithm: this.newRule.algorithm,
name: this.newRule.name,
privateport: this.newRule.privateport,
protocol: this.newRule.protocol,
publicport: this.newRule.publicport,
cidrlist: this.newRule.cidrlist
}).then(response => {
this.addVmModalVisible = false
this.handleAssignToLBRule(response.createloadbalancerruleresponse.id)
}).catch(error => {
this.$notifyError(error)
this.loading = false
})
// assigntoloadbalancerruleresponse.jobid
},
closeModal () {
this.selectedRule = null
this.tagsModalVisible = false
this.stickinessModalVisible = false
this.stickinessModalLoading = false
this.selectedStickinessPolicy = null
this.stickinessPolicyMethod = 'LbCookie'
this.editRuleModalVisible = false
this.editRuleModalLoading = false
this.addVmModalLoading = false
this.addVmModalNicLoading = false
this.showConfirmationAction = false
this.vms = []
this.nics = []
this.addVmModalVisible = false
this.newRule.virtualmachineid = []
},
handleChangePage (page, pageSize) {
this.page = page
this.pageSize = pageSize
this.fetchData()
},
handleChangePageSize (currentPage, pageSize) {
this.page = currentPage
this.pageSize = pageSize
this.fetchData()
},
handleChangeVmPage (page, pageSize) {
this.vmPage = page
this.vmPageSize = pageSize
this.fetchVirtualMachines()
},
handleChangeVmPageSize (currentPage, pageSize) {
this.vmPage = currentPage
this.vmPageSize = pageSize
this.fetchVirtualMachines()
},
onSearch (value) {
this.searchQuery = value
this.fetchVirtualMachines()
},
fetchLBTungstenFabricHealthMonitor () {
if (!('listTungstenFabricLBHealthMonitor' in this.$store.getters.apis)) {
return
}
this.tungstenHealthMonitors = []
this.loading = true
this.lbRules.forEach(rule => {
api('listTungstenFabricLBHealthMonitor', {
listAll: true,
lbruleid: rule.id
}).then(response => {
const healthmonitor = response?.listtungstenfabriclbhealthmonitorresponse?.healthmonitor || []
if (healthmonitor.length > 0) {
healthmonitor[0].lbruleid = rule.id
this.tungstenHealthMonitors.push(...healthmonitor)
}
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
})
},
returnHealthMonitorLabel (id) {
const match = this.tungstenHealthMonitors.filter(item => item.lbruleid === id)
if (match.length > 0) {
return match[0].type
}
return this.$t('label.configure')
},
openHealthMonitorModal (id) {
const match = this.tungstenHealthMonitors.filter(item => item.lbruleid === id)
this.healthMonitorParams.lbruleid = id
if (match.length > 0) {
this.healthMonitorParams.type = match[0].type
this.healthMonitorParams.retry = match[0].retry
this.healthMonitorParams.timeout = match[0].timeout
this.healthMonitorParams.interval = match[0].interval
this.healthMonitorParams.httpmethodtype = match[0].httpmethod
this.healthMonitorParams.expectedcode = match[0].expectedcode
this.healthMonitorParams.urlpath = match[0].urlpath
}
this.initMonitorForm()
this.healthMonitorModal = true
},
closeMonitorModal () {
this.healthMonitorModal = false
this.healthMonitorParams = {
type: 'PING',
retry: 3,
timeout: 5,
interval: 5,
httpmethodtype: 'GET',
expectedcode: undefined,
urlpath: '/'
}
},
handleConfigHealthMonitor () {
if (this.healthMonitorLoading) return
this.monitorRef.value.validate().then(() => {
const values = toRaw(this.monitorForm)
this.healthMonitorParams.type = values.type
this.healthMonitorParams.retry = values.retry
this.healthMonitorParams.timeout = values.timeout
this.healthMonitorParams.interval = values.interval
if (values.type === 'HTTP') {
this.healthMonitorParams.httpmethodtype = values.httpmethodtype
this.healthMonitorParams.expectedcode = values.expectedcode
this.healthMonitorParams.urlpath = values.urlpath
}
this.healthMonitorLoading = true
api('updateTungstenFabricLBHealthMonitor', this.healthMonitorParams).then(json => {
const jobId = json?.updatetungstenfabriclbhealthmonitorresponse?.jobid
this.$pollJob({
jobId: jobId,
successMessage: this.$t('message.success.config.health.monitor'),
successMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeMonitorModal()
this.healthMonitorLoading = false
},
errorMessage: this.$t('message.config.health.monitor.failed'),
errorMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeMonitorModal()
this.healthMonitorLoading = false
},
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.parentToggleLoading()
this.fetchData()
this.closeMonitorModal()
this.healthMonitorLoading = false
}
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.healthMonitorLoading = false
})
}).catch((error) => {
this.monitorRef.value.scrollToField(error.errorFields[0].name)
}).finally(() => {
this.healthMonitorLoading = false
})
}
}
}
</script>
<style lang="scss" scoped>
.rule {
&-container {
display: flex;
flex-direction: column;
width: 100%;
@media (min-width: 760px) {
margin-right: -20px;
margin-bottom: -10px;
}
}
&__row {
display: flex;
flex-wrap: wrap;
}
&__item {
padding-right: 20px;
margin-bottom: 20px;
@media (min-width: 760px) {
flex: 1;
}
}
&__title {
font-weight: bold;
}
}
.add-btn {
width: 100%;
padding-top: 15px;
padding-bottom: 15px;
height: auto;
}
.add-actions {
display: flex;
justify-content: flex-end;
margin-right: -20px;
margin-bottom: 20px;
@media (min-width: 760px) {
margin-top: 20px;
}
button {
margin-right: 20px;
}
}
.form {
display: flex;
margin-right: -20px;
flex-direction: column;
align-items: flex-start;
@media (min-width: 760px) {
flex-direction: row;
}
&__required {
margin-right: 5px;
color: red;
}
.error-text {
display: none;
color: red;
font-size: 0.8rem;
}
.error {
input {
border-color: red;
}
.error-text {
display: block;
}
}
&--column {
flex-direction: column;
margin-right: 0;
align-items: flex-end;
.form__item {
width: 100%;
padding-right: 0;
}
}
&__item {
display: flex;
flex-direction: column;
padding-right: 20px;
margin-bottom: 20px;
@media (min-width: 1200px) {
margin-bottom: 0;
flex: 1;
}
input,
.ant-select {
margin-top: auto;
}
&__input-container {
display: flex;
input {
&:not(:last-child) {
margin-right: 10px;
}
}
}
}
&__label {
font-weight: bold;
}
}
.rule-action {
margin-bottom: 10px;
}
.tags-modal {
.ant-divider {
margin-top: 0;
}
}
.tags {
margin-bottom: 10px;
}
.add-tags {
display: flex;
align-items: center;
justify-content: space-between;
&__input {
margin-right: 10px;
}
&__label {
margin-bottom: 5px;
font-weight: bold;
}
}
.tags-container {
display: flex;
flex-wrap: wrap;
margin-bottom: 10px;
}
.add-tags-done {
display: block;
margin-left: auto;
}
.modal-loading {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0,0,0,0.5);
z-index: 1;
color: #1890ff;
font-size: 2rem;
}
.ant-list-item {
display: flex;
flex-direction: column;
align-items: flex-start;
@media (min-width: 760px) {
flex-direction: row;
align-items: center;
}
}
.rule-instance-collapse {
width: 100%;
margin-left: -15px;
.ant-collapse-item {
border: 0;
}
}
.rule-instance-list {
display: flex;
flex-direction: column;
&__item {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
div {
margin-left: 25px;
margin-bottom: 10px;
}
}
}
.edit-rule {
.ant-select {
width: 100%;
}
&__item {
margin-bottom: 10px;
}
&__label {
margin-bottom: 5px;
font-weight: bold;
}
}
.vm-modal {
&__header {
display: flex;
span {
flex: 1;
font-weight: bold;
margin-right: 10px;
}
}
&__item {
display: flex;
margin-top: 10px;
span,
label {
display: block;
flex: 1;
margin-right: 10px;
}
}
}
.custom-ant-form {
.ant-form-item-label {
font-weight: bold;
line-height: 1;
}
.ant-form-item {
margin-bottom: 10px;
}
}
.custom-ant-list {
.ant-list-item-action {
margin-top: 10px;
margin-left: 0;
@media (min-width: 760px) {
margin-top: 0;
margin-left: 24px;
}
}
}
.rule-instance-collapse {
.ant-collapse-header,
.ant-collapse-content {
margin-left: -12px;
}
}
.rule {
.ant-list-item-content-single {
width: 100%;
@media (min-width: 760px) {
width: auto;
}
}
}
.pagination {
margin-top: 20px;
text-align: right;
}
.actions {
button {
&:not(:last-child) {
margin-right: 10px;
}
}
}
.list-view {
overflow-y: auto;
display: block;
width: 100%;
}
.filter {
display: block;
width: 240px;
margin-bottom: 10px;
}
.input-search {
margin-bottom: 10px;
width: 50%;
float: right;
}
</style>