network: Egress, PF, FW, VPN, LB tabs (#84)

Implements the egress, pf, fw, vpn and lb tabs for a guest network (ip).

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
Co-authored-by: Rohit Yadav <rohit@apache.org>
This commit is contained in:
Ritchie Vincent 2020-01-21 07:56:30 +00:00 committed by Rohit Yadav
parent 759921ee11
commit f3858e5297
14 changed files with 3041 additions and 147 deletions

View File

@ -2,24 +2,16 @@ addNetworkServiceProvider
addResourceDetail
addTrafficType
assignCertToLoadBalancer
assignToLoadBalancerRule
authorizeSamlSso
authorizeSecurityGroupEgress
authorizeSecurityGroupIngress
configureInternalLoadBalancerElement
configureVirtualRouterElement
createEgressFirewallRule
createFirewallRule
createLBHealthCheckPolicy
createLBStickinessPolicy
createLoadBalancer
createLoadBalancerRule
createManagementNetworkIpRange
createNetworkACL
createNetworkACLList
createPhysicalNetwork
createPortableIpRange
createPortForwardingRule
createPrivateGateway
createSecondaryStagingStore
createSnapshotFromVMSnapshot
@ -30,25 +22,17 @@ createVpnConnection
createVpnGateway
dedicateGuestVlanRange
dedicatePublicIpRange
deleteAccountFromProject
deleteEgressFirewallRule
deleteFirewallRule
deleteLBHealthCheckPolicy
deleteLBStickinessPolicy
deleteLdapConfiguration
deleteLoadBalancer
deleteLoadBalancerRule
deleteManagementNetworkIpRange
deleteNetworkACL
deleteNetworkACLList
deleteNetworkServiceProvider
deletePhysicalNetwork
deletePortableIpRange
deletePortForwardingRule
deletePrivateGateway
deleteProjectInvitation
deleteSecondaryStagingStore
deleteSnapshotPolicies
deleteStaticRoute
deleteStorageNetworkIpRange
deleteVlanIpRange
@ -57,74 +41,47 @@ deleteVpnGateway
findStoragePoolsForMigration
importLdapUsers
ldapCreateAccount
linkDomainToLdap
listAffinityGroupTypes
listAndSwitchSamlAccount
listCapabilities
listDedicatedClusters
listDedicatedGuestVlanRanges
listDedicatedHosts
listDedicatedPods
listDedicatedZones
listDeploymentPlanners
listEgressFirewallRules
listFirewallRules
listHostHAProviders
listHostTags
listHypervisors
listIdps
listInternalLoadBalancerElements
listInternalLoadBalancerVMs
listLBHealthCheckPolicies
listLBStickinessPolicies
listLdapUsers
listLoadBalancerRuleInstances
listLoadBalancerRules
listLoadBalancers
listNetworkACLLists
listNetworkACLs
listNetworkServiceProviders
listOsCategories
listPortableIpRanges
listPortForwardingRules
listPrivateGateways
listProjectAccounts
listProjectInvitations
listRegisteredServicePackages
listRemoteAccessVpns
listResourceLimits
listSamlAuthorization
listSecondaryStagingStores
listSnapshotPolicies
listStaticRoutes
listStorageNetworkIpRange
listStorageProviders
listStorageTags
listSupportedNetworkServices
listTemplateOvfProperties
listTemplatePermissions
listTrafficTypes
listVirtualRouterElements
listVlanIpRanges
listVmwareDcs
listVpnConnections
listVpnGateways
moveNetworkAclItem
releaseDedicatedGuestVlanRange
releasePublicIpRange
removeFromLoadBalancerRule
replaceNetworkACLList
resetVpnConnection
revokeSecurityGroupEgress
revokeSecurityGroupIngress
startInternalLoadBalancerVM
stopInternalLoadBalancerVM
updateLoadBalancerRule
updateNetworkACLItem
updateNetworkACLList
updateNetworkServiceProvider
updatePhysicalNetwork
updateProjectInvitation
updateResourceLimit
updateTrafficType
updateVpnCustomerGateway

View File

@ -13,8 +13,8 @@ var loadLabel = function (allFields, fieldDict, prefix) {
allFields[fieldId].components.push(prefix)
} else {
allFields[fieldId] = {
'labels': [fieldDict[fieldId].label],
'components': [prefix]
labels: [fieldDict[fieldId].label],
components: [prefix]
}
}
cols = cols + "'" + fieldId + "', "
@ -28,8 +28,8 @@ var loadLabel = function (allFields, fieldDict, prefix) {
allFields[colId].components.push(prefix)
} else {
allFields[colId] = {
'labels': [columns[colId].label],
'components': [prefix]
labels: [columns[colId].label],
components: [prefix]
}
}
})
@ -63,9 +63,9 @@ var loadFields = function (data, prefix) {
var curActions = []
$.each(Object.keys(acVal), function (idx, acKey) {
if (acVal[acKey].createForm) {
curActions.push({ 'action': acKey, 'label': acVal[acKey].label, 'keys': acVal[acKey].createForm.fields })
curActions.push({ action: acKey, label: acVal[acKey].label, keys: acVal[acKey].createForm.fields })
} else {
curActions.push({ 'action': acKey, 'label': acVal[acKey].label })
curActions.push({ action: acKey, label: acVal[acKey].label })
}
})
countActions = countActions + curActions.length
@ -77,5 +77,5 @@ var loadFields = function (data, prefix) {
$.extend(actions, recRes.actions)
}
})
return { 'allFields': allFields, 'columnsOrder': columnsOrder, 'actions': actions }
return { allFields: allFields, columnsOrder: columnsOrder, actions: actions }
}

View File

@ -29,7 +29,7 @@
</div>
<slot name="name">
<h4 class="name">
{{ resource.displayname || resource.name }}
{{ resource.displayname || resource.name || resource.displaytext || resource.hostname || resource.username || resource.ipaddress }}
</h4>
<console style="margin-left: 10px" :resource="resource" size="default" v-if="resource.id" />
</slot>

View File

@ -36,7 +36,7 @@
v-for="tab in tabs"
:tab="$t(tab.name)"
:key="tab.name"
v-if="'show' in tab ? tab.show(resource, $route, $store.getters.userInfo) : true">
v-if="showHideTab(tab)">
<component :is="tab.component" :resource="resource" :loading="loading" :tab="activeTab" />
</a-tab-pane>
</a-tabs>
@ -49,6 +49,7 @@
import DetailsTab from '@/components/view/DetailsTab'
import InfoCard from '@/components/view/InfoCard'
import ResourceLayout from '@/layouts/ResourceLayout'
import { api } from '@/api'
export default {
name: 'ResourceView',
@ -77,12 +78,45 @@ export default {
},
data () {
return {
activeTab: ''
activeTab: '',
networkService: null,
vpnEnabled: false
}
},
watch: {
resource: function (newItem, oldItem) {
this.resource = newItem
if (newItem.id === oldItem.id) return
if (this.resource.associatednetworkid) {
api('listNetworks', { id: this.resource.associatednetworkid }).then(response => {
this.networkService = response.listnetworksresponse.network[0]
})
}
if (this.resource.id && this.resource.ipaddress) {
api('listRemoteAccessVpns', {
publicipid: this.resource.id,
listAll: true
}).then(response => {
this.vpnEnabled = response.listremoteaccessvpnsresponse.remoteaccessvpn && response.listremoteaccessvpnsresponse.remoteaccessvpn.length > 0
})
}
}
},
methods: {
onTabChange (key) {
this.activeTab = key
},
showHideTab (tab) {
if ('networkServiceFilter' in tab) {
return this.networkService && this.networkService.service &&
tab.networkServiceFilter(this.networkService.service)
} else if ('show' in tab) {
return tab.show(this.resource, this.$route, this.$store.getters.userInfo)
} else {
return true
}
}
}
}

View File

@ -45,8 +45,9 @@ export default {
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'egress-rules',
component: () => import('@/views/network/EgressConfigure.vue')
name: 'Egress Rules',
component: () => import('@/views/network/EgressConfigure.vue'),
show: () => true
}],
actions: [
{
@ -227,14 +228,23 @@ export default {
columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'],
details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename'],
tabs: [{
name: 'configure',
component: () => import('@/views/network/IpConfigure.vue')
}, {
name: 'vpn',
component: () => import('@/views/network/VpnDetails.vue')
}, {
name: 'details',
component: () => import('@/components/view/DetailsTab.vue')
}, {
name: 'Firewall',
component: () => import('@/views/network/FirewallRules.vue'),
networkServiceFilter: networkService => networkService.filter(x => x.name === 'Firewall').length > 0
}, {
name: 'Port Forwarding',
component: () => import('@/views/network/PortForwarding.vue'),
networkServiceFilter: networkService => networkService.filter(x => x.name === 'PortForwarding').length > 0
}, {
name: 'Load Balancing',
component: () => import('@/views/network/LoadBalancing.vue'),
networkServiceFilter: networkService => networkService.filter(x => x.name === 'Lb').length > 0
}, {
name: 'VPN',
component: () => import('@/views/network/VpnDetails.vue')
}],
actions: [
{
@ -244,39 +254,6 @@ export default {
listView: true,
args: ['networkid']
},
{
api: 'createRemoteAccessVpn',
icon: 'link',
label: 'Enable Remote Access VPN',
dataView: true,
args: ['publicipid', 'domainid', 'account'],
mapping: {
publicipid: {
value: (record) => { return record.id }
},
domainid: {
value: (record) => { return record.domainid }
},
account: {
value: (record) => { return record.account }
}
}
},
{
api: 'deleteRemoteAccessVpn',
icon: 'disconnect',
label: 'Disable Remove Access VPN',
dataView: true,
args: ['publicipid', 'domainid'],
mapping: {
publicipid: {
value: (record) => { return record.id }
},
domainid: {
value: (record) => { return record.domainid }
}
}
},
{
api: 'enableStaticNat',
icon: 'plus-circle',
@ -306,7 +283,7 @@ export default {
{
api: 'disassociateIpAddress',
icon: 'delete',
label: 'Delete IP',
label: 'Release IP',
dataView: true,
show: (record) => { return !record.issourcenat }
}

View File

@ -2,6 +2,7 @@
"Accounts": "Accounts",
"Affinity Groups": "Affinity Groups",
"Alerts": "Alerts",
"cancel": "Cancel",
"CPU Sockets": "CPU Sockets",
"Cloudian Storage": "Cloudian Storage",
"Clusters": "Clusters",
@ -13,6 +14,7 @@
"Dashboard": "Dashboard",
"Disk Offerings": "Disk Offerings",
"Domains": "Domains",
"done": "Done",
"Events": "Events",
"Global Settings": "Global Settings",
"Hosts": "Hosts",
@ -77,6 +79,7 @@
"agentUsername": "Agent Username",
"agentstate": "Agent State",
"algorithm": "Algorithm",
"all": "All",
"allocatediops": "IOPS Allocated",
"allocationstate": "Allocation State",
"annotation": "Annotation",
@ -256,6 +259,7 @@
"hypervisortype": "Hypervisor",
"hypervisorversion": "Hypervisor version",
"hypervnetworklabel": "HyperV Traffic Label",
"icmp": "ICMP",
"icmpcode": "ICMP Code",
"icmptype": "ICMP Type",
"id": "ID",
@ -340,6 +344,7 @@
"label.action.cancel.maintenance.mode": "Cancel Maintenance Mode",
"label.action.change.password": "Change Password",
"label.action.configure.samlauthorization": "Configure SAML SSO Authorization",
"label.action.configure.stickiness": "Stickiness",
"label.action.copy.ISO": "Copy ISO",
"label.action.copy.template": "Copy Template",
"label.action.create.volume": "Create Volume",
@ -430,6 +435,8 @@
"label.add.OpenDaylight.device": "Add OpenDaylight Controller",
"label.add.PA.device": "Add Palo Alto device",
"label.add.SRX.device": "Add SRX device",
"label.add.VM": "Add VM",
"label.add.VMs": "Add VMs",
"label.add.VM.to.tier": "Add VM to tier",
"label.add.account": "Add Account",
"label.add.acl.list": "Add ACL List",
@ -460,8 +467,10 @@
"label.add.primary.storage": "Add Primary Storage",
"label.add.region": "Add Region",
"label.add.role": "Add Role",
"label.add.rule": "Add Rule",
"label.add.secondary.storage": "Add Secondary Storage",
"label.add.security.group": "Add Security Group",
"label.add.setting": "Add Setting",
"label.add.system.service.offering": "Add System Service Offering",
"label.add.ucs.manager": "Add UCS Manager",
"label.add.user": "Add User",
@ -840,6 +849,7 @@
"secretkey": "Secret Key",
"securityGroups": "Security Groups",
"securitygroup": "Security Group",
"select": "Select",
"sent": "Date",
"sentbytes": "Bytes Sent",
"server": "Server",
@ -872,6 +882,7 @@
"snmpCommunity": "SNMP Community",
"snmpPort": "SNMP Port",
"sockettimeout": "Socket Timeout",
"sourcecidr": "Source CIDR",
"sourceNat": "Source NAT",
"sourceipaddress": "Source IP Address",
"sourceport": "Source Port",
@ -905,6 +916,7 @@
"systemvmtype": "System VM Type",
"tags": "Tags",
"tariffValue": "Tariff Value",
"tcp": "TCP",
"template": "Select a template",
"templateFileUpload": "Local file",
"templateLimit": "Template Limits",
@ -926,6 +938,7 @@
"traffictype": "Traffic Type",
"transportzoneuuid": "Transport Zone Uuid",
"type": "Type",
"udp": "UDP",
"unit": "Usage Unit",
"url": "URL",
"usageName": "Usage Type",
@ -964,6 +977,7 @@
"vlanRange": "VLAN/VNI Range",
"vlanname": "VLAN",
"vlanrange": "VLAN/VNI Range",
"vm": "VM",
"vmLimit": "Instance Limits",
"vmTotal": "Instances",
"vmdisplayname": "VM display name",

View File

@ -45,6 +45,6 @@ export const deviceEnquire = function (callback) {
// screen and (max-width: 1087.99px)
enquireJs
.register('screen and (max-width: 576px)', matchMobile)
.register('screen and (min-width: 576px) and (max-width: 1366px)', matchTablet)
.register('screen and (min-width: 1367px)', matchDesktop)
.register('screen and (min-width: 576px) and (max-width: 1280px)', matchTablet)
.register('screen and (min-width: 1281px)', matchDesktop)
}

View File

@ -17,24 +17,287 @@
<template>
<div>
TODO: Egress view for isolated network
<div>
<div class="form">
<div class="form__item">
<div class="form__label">Source CIDR</div>
<a-input v-model="newRule.cidrlist"></a-input>
</div>
<div class="form__item">
<div class="form__label">Destination CIDR</div>
<a-input v-model="newRule.destcidrlist"></a-input>
</div>
<div class="form__item">
<div class="form__label">Protocol</div>
<a-select v-model="newRule.protocol" style="width: 100%;" @change="resetRulePorts">
<a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="udp">UDP</a-select-option>
<a-select-option value="icmp">ICMP</a-select-option>
<a-select-option value="all">All</a-select-option>
</a-select>
</div>
<div v-show="newRule.protocol === 'tcp' || newRule.protocol === 'udp'" class="form__item">
<div class="form__label">Start Port</div>
<a-input v-model="newRule.startport"></a-input>
</div>
<div v-show="newRule.protocol === 'tcp' || newRule.protocol === 'udp'" class="form__item">
<div class="form__label">End Port</div>
<a-input v-model="newRule.endport"></a-input>
</div>
<div v-show="newRule.protocol === 'icmp'" class="form__item">
<div class="form__label">ICMP Type</div>
<a-input v-model="newRule.icmptype"></a-input>
</div>
<div v-show="newRule.protocol === 'icmp'" class="form__item">
<div class="form__label">ICMP Code</div>
<a-input v-model="newRule.icmpcode"></a-input>
</div>
<div class="form__item">
<a-button type="primary" icon="plus" @click="addRule">{{ $t('add') }}</a-button>
</div>
</div>
</div>
<a-divider/>
<a-list :loading="loading" style="min-height: 25px;">
<a-list-item v-for="rule in egressRules" :key="rule.id" class="rule">
<div class="rule-container">
<div class="rule__item">
<div class="rule__title">Source CIDR</div>
<div>{{ rule.cidrlist }}</div>
</div>
<div class="rule__item">
<div class="rule__title">Destination CIDR</div>
<div>{{ rule.destcidrlist }}</div>
</div>
<div class="rule__item">
<div class="rule__title">Protocol</div>
<div>{{ rule.protocol | capitalise }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ rule.protocol === 'icmp' ? 'ICMP Type' : 'Start Port' }}</div>
<div>{{ rule.icmptype || rule.startport >= 0 ? rule.icmptype || rule.startport : 'All' }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ rule.protocol === 'icmp' ? 'ICMP Code' : 'End Port' }}</div>
<div>{{ rule.icmpcode || rule.endport >= 0 ? rule.icmpcode || rule.endport : 'All' }}</div>
</div>
<div slot="actions">
<a-button shape="round" type="danger" icon="delete" @click="deleteRule(rule)" />
</div>
</div>
</a-list-item>
</a-list>
</div>
</template>
<script>
import { api } from '@/api'
export default {
name: '',
components: {
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
loading: true,
egressRules: [],
newRule: {
protocol: 'tcp',
cidrlist: null,
destcidrlist: null,
networkid: this.resource.id,
icmptype: null,
icmpcode: null,
startport: null,
endport: null
}
}
},
mounted () {
this.fetchData()
},
filters: {
capitalise: val => {
if (val === 'all') return 'All'
return val.toUpperCase()
}
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.resource = newItem
this.fetchData()
}
},
methods: {
fetchData () {
this.loading = true
api('listEgressFirewallRules', {
listAll: true,
networkid: this.resource.id
}).then(response => {
this.egressRules = response.listegressfirewallrulesresponse.firewallrule
this.loading = false
})
},
deleteRule (rule) {
this.loading = true
api('deleteEgressFirewallRule', { id: rule.id }).then(response => {
this.$pollJob({
jobId: response.deleteegressfirewallruleresponse.jobid,
successMessage: `Successfully removed Egress rule`,
successMethod: () => this.fetchData(),
errorMessage: 'Removing Egress rule failed',
errorMethod: () => this.fetchData(),
loadingMessage: `Deleting Egress rule...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => this.fetchData()
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
this.fetchData()
})
},
addRule () {
this.loading = true
api('createEgressFirewallRule', { ...this.newRule }).then(response => {
this.$pollJob({
jobId: response.createegressfirewallruleresponse.jobid,
successMessage: `Successfully added new Egress rule`,
successMethod: () => {
this.resetAllRules()
this.fetchData()
},
errorMessage: 'Adding new Egress rule failed',
errorMethod: () => {
this.resetAllRules()
this.fetchData()
},
loadingMessage: `Adding new Egress rule...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.resetAllRules()
this.fetchData()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.createegressfirewallruleresponse.errortext
})
this.resetAllRules()
this.fetchData()
})
},
resetAllRules () {
this.newRule.protocol = 'tcp'
this.newRule.cidrlist = null
this.newRule.destcidrlist = null
this.newRule.networkid = this.resource.id
this.resetRulePorts()
},
resetRulePorts () {
this.newRule.icmptype = null
this.newRule.icmpcode = null
this.newRule.startport = null
this.newRule.endport = null
}
}
}
</script>
<style scoped>
<style scoped lang="scss">
.rule {
&-container {
display: flex;
width: 100%;
flex-wrap: wrap;
margin-right: -20px;
margin-bottom: -10px;
}
&__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;
margin-bottom: 20px;
flex-direction: column;
align-items: flex-end;
@media (min-width: 760px) {
flex-direction: row;
}
&__item {
display: flex;
flex-direction: column;
flex: 1;
padding-right: 20px;
margin-bottom: 20px;
@media (min-width: 760px) {
margin-bottom: 0;
}
input,
.ant-select {
margin-top: auto;
}
}
&__label {
font-weight: bold;
}
}
</style>

View File

@ -0,0 +1,476 @@
// 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>
<div class="form">
<div class="form__item">
<div class="form__label">{{ $t('sourcecidr') }}</div>
<a-input v-model="newRule.cidrlist"></a-input>
</div>
<div class="form__item">
<div class="form__label">{{ $t('protocol') }}</div>
<a-select v-model="newRule.protocol" style="width: 100%;" @change="resetRulePorts">
<a-select-option value="tcp">{{ $t('tcp') }}</a-select-option>
<a-select-option value="udp">{{ $t('udp') }}</a-select-option>
<a-select-option value="icmp">{{ $t('icmp') }}</a-select-option>
</a-select>
</div>
<div v-show="newRule.protocol === 'tcp' || newRule.protocol === 'udp'" class="form__item">
<div class="form__label">{{ $t('startport') }}</div>
<a-input v-model="newRule.startport"></a-input>
</div>
<div v-show="newRule.protocol === 'tcp' || newRule.protocol === 'udp'" class="form__item">
<div class="form__label">{{ $t('endport') }}</div>
<a-input v-model="newRule.endport"></a-input>
</div>
<div v-show="newRule.protocol === 'icmp'" class="form__item">
<div class="form__label">{{ $t('icmptype') }}</div>
<a-input v-model="newRule.icmptype"></a-input>
</div>
<div v-show="newRule.protocol === 'icmp'" class="form__item">
<div class="form__label">{{ $t('icmpcode') }}</div>
<a-input v-model="newRule.icmpcode"></a-input>
</div>
<div class="form__item" style="margin-left: auto;">
<a-button type="primary" @click="addRule">{{ $t('add') }}</a-button>
</div>
</div>
</div>
<a-divider/>
<a-list :loading="loading" style="min-height: 25px;">
<a-list-item v-for="rule in firewallRules" :key="rule.id" class="rule">
<div class="rule-container">
<div class="rule__item">
<div class="rule__title">{{ $t('sourcecidr') }}</div>
<div>{{ rule.cidrlist }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ $t('protocol') }}</div>
<div>{{ rule.protocol | capitalise }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ rule.protocol === 'icmp' ? $t('icmptype') : $t('startport') }}</div>
<div>{{ rule.icmptype || rule.startport >= 0 ? rule.icmptype || rule.startport : $t('all') }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ rule.protocol === 'icmp' ? 'ICMP Code' : 'End Port' }}</div>
<div>{{ rule.icmpcode || rule.endport >= 0 ? rule.icmpcode || rule.endport : $t('all') }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ $t('state') }}</div>
<div>{{ rule.state }}</div>
</div>
<div slot="actions">
<a-button shape="round" icon="tag" class="rule-action" @click="() => openTagsModal(rule.id)" />
<a-button shape="round" type="danger" icon="delete" class="rule-action" @click="deleteRule(rule)" />
</div>
</div>
</a-list-item>
</a-list>
<a-modal title="Edit Tags" v-model="tagsModalVisible" :footer="null" :afterClose="closeModal">
<div class="add-tags">
<div class="add-tags__input">
<p class="add-tags__label">{{ $t('key') }}</p>
<a-input v-model="newTag.key"></a-input>
</div>
<div class="add-tags__input">
<p class="add-tags__label">{{ $t('value') }}</p>
<a-input v-model="newTag.value"></a-input>
</div>
<a-button type="primary" @click="() => handleAddTag()">{{ $t('add') }}</a-button>
</div>
<a-divider></a-divider>
<div class="tags-container">
<div class="tags" v-for="(tag, index) in tags" :key="index">
<a-tag :key="index" :closable="true" :afterClose="() => handleDeleteTag(tag)">
{{ tag.key }} = {{ tag.value }}
</a-tag>
</div>
</div>
<a-button class="add-tags-done" @click="tagsModalVisible = false" type="primary">{{ $t('done') }}</a-button>
</a-modal>
</div>
</template>
<script>
import { api } from '@/api'
export default {
props: {
resource: {
type: Object,
required: true
}
},
inject: ['parentFetchData', 'parentToggleLoading'],
data () {
return {
loading: true,
firewallRules: [],
newRule: {
protocol: 'tcp',
cidrlist: null,
ipaddressid: this.resource.id,
icmptype: null,
icmpcode: null,
startport: null,
endport: null
},
tagsModalVisible: false,
selectedRule: null,
tags: [],
newTag: {
key: null,
value: null
}
}
},
mounted () {
this.fetchData()
},
filters: {
capitalise: val => {
if (val === 'all') return 'All'
return val.toUpperCase()
}
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.resource = newItem
this.fetchData()
}
},
methods: {
fetchData () {
this.loading = true
api('listFirewallRules', {
listAll: true,
ipaddressid: this.resource.id
}).then(response => {
this.firewallRules = response.listfirewallrulesresponse.firewallrule
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
}).finally(() => {
this.loading = false
})
},
deleteRule (rule) {
this.loading = true
api('deleteFirewallRule', { id: rule.id }).then(response => {
this.$pollJob({
jobId: response.deletefirewallruleresponse.jobid,
successMessage: `Successfully removed Firewall rule`,
successMethod: () => this.fetchData(),
errorMessage: 'Removing Firewall rule failed',
errorMethod: () => this.fetchData(),
loadingMessage: `Deleting Firewall rule...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => this.fetchData()
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
this.fetchData()
})
},
addRule () {
this.loading = true
api('createFirewallRule', { ...this.newRule }).then(response => {
this.$pollJob({
jobId: response.createfirewallruleresponse.jobid,
successMessage: `Successfully added new Firewall rule`,
successMethod: () => {
this.resetAllRules()
this.fetchData()
},
errorMessage: 'Adding new Firewall rule failed',
errorMethod: () => {
this.resetAllRules()
this.fetchData()
},
loadingMessage: `Adding new Firewall rule...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.resetAllRules()
this.fetchData()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.createfirewallruleresponse.errortext
})
this.resetAllRules()
this.fetchData()
})
},
resetAllRules () {
this.newRule.protocol = 'tcp'
this.newRule.cidrlist = null
this.newRule.networkid = this.resource.id
this.resetRulePorts()
},
resetRulePorts () {
this.newRule.icmptype = null
this.newRule.icmpcode = null
this.newRule.startport = null
this.newRule.endport = null
},
closeModal () {
this.selectedRule = null
this.tagsModalVisible = false
this.newTag.key = null
this.newTag.value = null
},
openTagsModal (id) {
this.selectedRule = id
this.tagsModalVisible = true
api('listTags', {
resourceId: id,
resourceType: 'FirewallRule',
listAll: true
}).then(response => {
this.tags = response.listtagsresponse.tag
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
this.closeModal()
})
},
handleAddTag () {
api('createTags', {
'tags[0].key': this.newTag.key,
'tags[0].value': this.newTag.value,
resourceIds: this.selectedRule,
resourceType: 'FirewallRule'
}).then(response => {
this.$pollJob({
jobId: response.createtagsresponse.jobid,
successMessage: `Successfully added tag`,
successMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.openTagsModal(this.selectedRule)
},
errorMessage: 'Failed to add new tag',
errorMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
},
loadingMessage: `Adding tag...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.createtagsresponse.errortext
})
this.closeModal()
})
},
handleDeleteTag (tag) {
api('deleteTags', {
'tags[0].key': tag.key,
'tags[0].value': tag.value,
resourceIds: this.selectedRule,
resourceType: 'FirewallRule'
}).then(response => {
this.$pollJob({
jobId: response.deletetagsresponse.jobid,
successMessage: `Successfully removed tag`,
successMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.openTagsModal(this.selectedRule)
},
errorMessage: 'Failed to remove tag',
errorMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
},
loadingMessage: `Removing tag...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.deletetagsresponse.errortext
})
this.closeModal()
})
}
}
}
</script>
<style scoped lang="scss">
.rule {
&-container {
display: flex;
width: 100%;
flex-wrap: wrap;
margin-right: -20px;
margin-bottom: -10px;
}
&__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;
align-items: flex-end;
margin-right: -20px;
flex-direction: column;
margin-bottom: 20px;
@media (min-width: 760px) {
flex-direction: row;
}
&__item {
display: flex;
flex-direction: column;
/*flex: 1;*/
padding-right: 20px;
margin-bottom: 20px;
@media (min-width: 760px) {
margin-bottom: 0;
}
input,
.ant-select {
margin-top: auto;
}
}
&__label {
font-weight: bold;
}
}
.rule-action {
margin-bottom: 20px;
&:not(:last-of-type) {
margin-right: 10px;
}
}
.tags {
margin-bottom: 10px;
}
.add-tags {
display: flex;
align-items: flex-end;
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;
}
</style>

View File

@ -107,7 +107,7 @@
okText="Yes"
cancelText="No"
>
<a-button shape="round" type="danger" icon="close-circle" class="rule-action" />
<a-button shape="round" type="danger" icon="delete" class="rule-action" />
</a-popconfirm>
</div>
</a-list-item>

View File

@ -1,40 +0,0 @@
// 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>
TODO: IP configure view: firewall, pf, lb
</div>
</template>
<script>
export default {
name: '',
components: {
},
data () {
return {
}
},
methods: {
}
}
</script>
<style scoped>
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,675 @@
// 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>
<div class="form">
<div class="form__item">
<div class="form__label">{{ $t('privateport') }}</div>
<a-input-group class="form__item__input-container" compact>
<a-input
v-model="newRule.privateport"
placeholder="Start"
style="border-right: 0; width: 60px; margin-right: 0;"></a-input>
<a-input
placeholder="-"
disabled
style="width: 30px; border-left: 0; border-right: 0; pointer-events: none; backgroundColor: #fff; text-align:
center; margin-right: 0;"></a-input>
<a-input
v-model="newRule.privateendport"
placeholder="End"
style="border-left: 0; width: 60px; text-align: right; margin-right: 0;"></a-input>
</a-input-group>
</div>
<div class="form__item">
<div class="form__label">{{ $t('publicport') }}</div>
<a-input-group class="form__item__input-container" compact>
<a-input
v-model="newRule.publicport"
placeholder="Start"
style="border-right: 0; width: 60px; margin-right: 0;"></a-input>
<a-input
placeholder="-"
disabled
style="width: 30px; border-left: 0; border-right: 0; pointer-events: none; backgroundColor: #fff;
text-align: center; margin-right: 0;"></a-input>
<a-input
v-model="newRule.publicendport"
placeholder="End"
style="border-left: 0; width: 60px; text-align: right; margin-right: 0;"></a-input>
</a-input-group>
</div>
<div class="form__item">
<div class="form__label">{{ $t('protocol') }}</div>
<a-select v-model="newRule.protocol" style="width: 100%;">
<a-select-option value="tcp">{{ $t('tcp') }}</a-select-option>
<a-select-option value="udp">{{ $t('udp') }}</a-select-option>
</a-select>
</div>
<div class="form__item" style="margin-left: auto;">
<div class="form__label">{{ $t('label.add.VM') }}</div>
<a-button type="primary" @click="openAddVMModal">{{ $t('add') }}</a-button>
</div>
</div>
</div>
<a-divider/>
<a-list :loading="loading" style="min-height: 25px;">
<a-list-item v-for="rule in portForwardRules" :key="rule.id" class="rule">
<div class="rule-container">
<div class="rule__item">
<div class="rule__title">{{ $t('privateport') }}</div>
<div>{{ rule.privateport }} - {{ rule.privateendport }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ $t('publicport') }}</div>
<div>{{ rule.publicport }} - {{ rule.publicendport }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ $t('protocol') }}</div>
<div>{{ rule.protocol | capitalise }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ $t('state') }}</div>
<div>{{ rule.state }}</div>
</div>
<div class="rule__item">
<div class="rule__title">{{ $t('vm') }}</div>
<div class="rule__title"></div>
<div><a-icon type="desktop"/> <router-link :to="{ path: '/vm/' + rule.virtualmachineid }">{{ rule.virtualmachinename }}</router-link> ({{ rule.vmguestip }})</div>
</div>
<div slot="actions">
<a-button shape="round" icon="tag" class="rule-action" @click="() => openTagsModal(rule.id)" />
<a-button shape="round" type="danger" icon="delete" class="rule-action" @click="deleteRule(rule)" />
</div>
</div>
</a-list-item>
</a-list>
<a-modal title="Edit Tags" v-model="tagsModalVisible" :footer="null" :afterClose="closeModal">
<span v-show="tagsModalLoading" class="tags-modal-loading">
<a-icon type="loading"></a-icon>
</span>
<div class="add-tags">
<div class="add-tags__input">
<p class="add-tags__label">{{ $t('key') }}</p>
<a-input v-model="newTag.key"></a-input>
</div>
<div class="add-tags__input">
<p class="add-tags__label">{{ $t('value') }}</p>
<a-input v-model="newTag.value"></a-input>
</div>
<a-button type="primary" @click="() => handleAddTag()">{{ $t('label.add') }}</a-button>
</div>
<a-divider></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="true" :afterClose="() => handleDeleteTag(tag)">
{{ tag.key }} = {{ tag.value }}
</a-tag>
</div>
</div>
<a-button class="add-tags-done" @click="tagsModalVisible = false" type="primary">{{ $t('done') }}</a-button>
</a-modal>
<a-modal
title="Add VM"
v-model="addVmModalVisible"
class="vm-modal"
width="60vw"
@ok="addRule"
:okButtonProps="{ props:
{disabled: newRule.virtualmachineid === null } }"
@cancel="closeModal"
>
<a-icon v-if="addVmModalLoading" type="loading"></a-icon>
<div v-else>
<div class="vm-modal__header">
<span style="min-width: 200px;">{{ $t('name') }}</span>
<span>{{ $t('instancename') }}</span>
<span>{{ $t('displayname') }}</span>
<span>{{ $t('ip') }}</span>
<span>{{ $t('account') }}</span>
<span>{{ $t('zone') }}</span>
<span>{{ $t('state') }}</span>
<span>{{ $t('select') }}</span>
</div>
<a-radio-group v-model="newRule.virtualmachineid" style="width: 100%;" @change="fetchNics">
<div v-for="(vm, index) in vms" :key="index" class="vm-modal__item">
<span style="min-width: 200px;">
<span>
{{ vm.name }}
</span>
<a-icon v-if="addVmModalNicLoading" type="loading"></a-icon>
<a-select
v-else-if="!addVmModalNicLoading && newRule.virtualmachineid === vm.id"
v-model="newRule.vmguestip">
<a-select-option v-for="(nic, nicIndex) in nics" :key="nic" :value="nic">
{{ nic }}{{ nicIndex === 0 ? ' (Primary)' : null }}
</a-select-option>
</a-select>
</span>
<span>{{ vm.instancename }}</span>
<span>{{ vm.displayname }}</span>
<span></span>
<span>{{ vm.account }}</span>
<span>{{ vm.zonename }}</span>
<span>{{ vm.state }}</span>
<a-radio :value="vm.id" />
</div>
</a-radio-group>
</div>
</a-modal>
</div>
</template>
<script>
import { api } from '@/api'
export default {
props: {
resource: {
type: Object,
required: true
}
},
inject: ['parentFetchData', 'parentToggleLoading'],
data () {
return {
loading: true,
portForwardRules: [],
newRule: {
protocol: 'tcp',
privateport: null,
privateendport: null,
publicport: null,
publicendport: null,
openfirewall: false,
vmguestip: null,
virtualmachineid: null
},
tagsModalVisible: false,
selectedRule: null,
tags: [],
newTag: {
key: null,
value: null
},
tagsModalLoading: false,
addVmModalVisible: false,
addVmModalLoading: false,
addVmModalNicLoading: false,
vms: [],
nics: []
}
},
mounted () {
this.fetchData()
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.resource = newItem
this.fetchData()
}
},
filters: {
capitalise: val => {
if (val === 'all') return 'All'
return val.toUpperCase()
}
},
methods: {
fetchData () {
this.loading = true
api('listPortForwardingRules', {
listAll: true,
ipaddressid: this.resource.id
}).then(response => {
this.portForwardRules = response.listportforwardingrulesresponse.portforwardingrule
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
}).finally(() => {
this.loading = false
})
},
deleteRule (rule) {
this.loading = true
api('deletePortForwardingRule', { id: rule.id }).then(response => {
this.$pollJob({
jobId: response.deleteportforwardingruleresponse.jobid,
successMessage: `Successfully removed Port Forwarding rule`,
successMethod: () => this.fetchData(),
errorMessage: 'Removing Port Forwarding rule failed',
errorMethod: () => this.fetchData(),
loadingMessage: `Deleting Port Forwarding rule...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => this.fetchData()
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
this.fetchData()
})
},
addRule () {
this.loading = true
this.addVmModalVisible = false
api('createPortForwardingRule', {
...this.newRule,
ipaddressid: this.resource.id,
networkid: this.resource.associatednetworkid
}).then(response => {
this.$pollJob({
jobId: response.createportforwardingruleresponse.jobid,
successMessage: `Successfully added new Port Forwarding rule`,
successMethod: () => {
this.closeModal()
this.fetchData()
},
errorMessage: 'Adding new Port Forwarding rule failed',
errorMethod: () => {
this.closeModal()
this.fetchData()
},
loadingMessage: `Adding new Port Forwarding rule...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.closeModal()
this.fetchData()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.createportforwardingruleresponse.errortext
})
this.closeModal()
this.fetchData()
})
},
resetAllRules () {
this.newRule.protocol = 'tcp'
this.newRule.privateport = null
this.newRule.privateendport = null
this.newRule.publicport = null
this.newRule.publicendport = null
this.newRule.openfirewall = false
this.newRule.vmguestip = null
this.newRule.virtualmachineid = null
},
resetTagInputs () {
this.newTag.key = null
this.newTag.value = null
},
closeModal () {
this.selectedRule = null
this.tagsModalVisible = false
this.addVmModalVisible = false
this.newRule.virtualmachineid = null
this.addVmModalLoading = false
this.addVmModalNicLoading = false
this.nics = []
this.resetTagInputs()
this.resetAllRules()
},
openTagsModal (id) {
this.tagsModalLoading = true
this.selectedRule = id
this.tagsModalVisible = true
this.tags = []
this.resetTagInputs()
api('listTags', {
resourceId: id,
resourceType: 'PortForwardingRule',
listAll: true
}).then(response => {
this.tags = response.listtagsresponse.tag
this.tagsModalLoading = false
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
this.closeModal()
})
},
handleAddTag () {
this.tagsModalLoading = true
api('createTags', {
'tags[0].key': this.newTag.key,
'tags[0].value': this.newTag.value,
resourceIds: this.selectedRule,
resourceType: 'PortForwardingRule'
}).then(response => {
this.$pollJob({
jobId: response.createtagsresponse.jobid,
successMessage: `Successfully added tag`,
successMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.openTagsModal(this.selectedRule)
},
errorMessage: 'Failed to add new tag',
errorMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
},
loadingMessage: `Adding tag...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.createtagsresponse.errortext
})
this.closeModal()
})
},
handleDeleteTag (tag) {
this.tagsModalLoading = true
api('deleteTags', {
'tags[0].key': tag.key,
'tags[0].value': tag.value,
resourceIds: this.selectedRule,
resourceType: 'PortForwardingRule'
}).then(response => {
this.$pollJob({
jobId: response.deletetagsresponse.jobid,
successMessage: `Successfully removed tag`,
successMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.openTagsModal(this.selectedRule)
},
errorMessage: 'Failed to remove tag',
errorMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
},
loadingMessage: `Removing tag...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.parentFetchData()
this.parentToggleLoading()
this.closeModal()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.deletetagsresponse.errortext
})
this.closeModal()
})
},
openAddVMModal () {
this.addVmModalVisible = true
this.addVmModalLoading = true
api('listVirtualMachines', {
listAll: true,
page: 1,
pagesize: 500,
networkid: this.resource.associatednetworkid,
account: this.resource.account,
domainid: this.resource.domainid
}).then(response => {
this.vms = response.listvirtualmachinesresponse.virtualmachine
this.addVmModalLoading = false
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
this.closeModal()
})
},
fetchNics (e) {
this.addVmModalNicLoading = true
api('listNics', {
virtualmachineid: e.target.value,
networkid: this.resource.associatednetworkid
}).then(response => {
if (!response.listnicsresponse.nic || response.listnicsresponse.nic.length < 1) return
const nic = response.listnicsresponse.nic[0]
this.nics.push(nic.ipaddress)
if (nic.secondaryip && nic.secondaryip.length > 0) {
this.nics.push(...nic.secondaryip.map(ip => ip.ipaddress))
}
this.newRule.vmguestip = this.nics[0]
this.addVmModalNicLoading = false
}).catch(error => {
console.log(error)
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
this.closeModal()
})
}
}
}
</script>
<style scoped lang="scss">
.rule {
&-container {
display: flex;
width: 100%;
flex-wrap: wrap;
margin-right: -20px;
margin-bottom: -10px;
}
&__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;
margin-bottom: 20px;
flex-direction: column;
@media (min-width: 760px) {
flex-direction: row;
}
&__item {
display: flex;
flex-direction: column;
/*flex: 1;*/
padding-right: 20px;
margin-bottom: 20px;
@media (min-width: 760px) {
margin-bottom: 0;
}
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: 20px;
&:not(:last-of-type) {
margin-right: 10px;
}
}
.tags {
margin-bottom: 10px;
}
.add-tags {
display: flex;
align-items: flex-end;
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;
}
.tags-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;
}
.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;
}
}
}
</style>

View File

@ -16,25 +16,190 @@
// under the License.
<template>
<div>
TODO: VPN configure/detail tab view
<div v-if="remoteAccessVpn">
<div>
<p>Your Remote Access VPN is currently enabled and can be accessed via the IP <strong>{{ remoteAccessVpn.publicip }}</strong></p>
<p>Your IPSec pre-shared key is <strong>{{ remoteAccessVpn.presharedkey }}</strong></p>
<a-divider/>
<a-button><router-link :to="{ path: '/vpnuser'}">Manage VPN Users</router-link></a-button>
<a-button style="margin-left: 10px" type="danger" @click="disableVpn = true">Disable VPN</a-button>
</div>
<a-modal v-model="disableVpn" :footer="null" oncancel="disableVpn = false" title="Disable Remove Access VPN">
<p>Are you sure you want to disable VPN?</p>
<a-divider></a-divider>
<div class="actions">
<a-button @click="() => disableVpn = false">Cancel</a-button>
<a-button type="primary" @click="handleDisableVpn">Yes</a-button>
</div>
</a-modal>
</div>
<div v-else>
<a-button type="primary" @click="enableVpn = true">Enable VPN</a-button>
<a-modal v-model="enableVpn" :footer="null" onCancel="enableVpn = false" title="Enable Remote Access VPN">
<p>Please confirm that you want Remote Access VPN enabled for this IP address.</p>
<a-divider></a-divider>
<div class="actions">
<a-button @click="() => enableVpn = false">Cancel</a-button>
<a-button type="primary" @click="handleCreateVpn">Yes</a-button>
</div>
</a-modal>
</div>
</template>
<script>
import { api } from '@/api'
export default {
name: '',
components: {
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
remoteAccessVpn: null,
enableVpn: false,
disableVpn: false
}
},
inject: ['parentFetchData', 'parentToggleLoading'],
mounted () {
this.fetchData()
},
watch: {
resource: function (newItem, oldItem) {
if (!newItem || !newItem.id) {
return
}
this.resource = newItem
this.fetchData()
}
},
methods: {
fetchData () {
api('listRemoteAccessVpns', {
publicipid: this.resource.id,
listAll: true
}).then(response => {
this.remoteAccessVpn = response.listremoteaccessvpnsresponse.remoteaccessvpn
? response.listremoteaccessvpnsresponse.remoteaccessvpn[0] : null
}).catch(error => {
console.log(error)
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
})
},
handleCreateVpn () {
this.parentToggleLoading()
this.enableVpn = false
api('createRemoteAccessVpn', {
publicipid: this.resource.id,
domainid: this.resource.domainid,
account: this.resource.account
}).then(response => {
this.$pollJob({
jobId: response.createremoteaccessvpnresponse.jobid,
successMethod: result => {
const res = result.jobresult.remoteaccessvpn
this.$notification.success({
message: 'Status',
description:
`Your Remote Access VPN is currently enabled and can be accessed via the IP ${res.publicip}. Your IPSec pre-shared key is ${res.presharedkey}`,
duration: 0
})
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
},
errorMessage: 'Failed to enable VPN',
errorMethod: () => {
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
},
loadingMessage: `Enabling VPN...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.createremoteaccessvpnresponse
? error.response.data.createremoteaccessvpnresponse.errortext : error.response.data.errorresponse.errortext
})
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
})
},
handleDisableVpn () {
this.parentToggleLoading()
this.disableVpn = false
api('deleteRemoteAccessVpn', {
publicipid: this.resource.id,
domainid: this.resource.domainid
}).then(response => {
this.$pollJob({
jobId: response.deleteremoteaccessvpnresponse.jobid,
successMessage: 'Successfully disabled VPN',
successMethod: () => {
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
},
errorMessage: 'Failed to disable VPN',
errorMethod: () => {
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
},
loadingMessage: `Disabling VPN...`,
catchMessage: 'Error encountered while fetching async job result',
catchMethod: () => {
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
}
})
}).catch(error => {
this.$notification.error({
message: `Error ${error.response.status}`,
description: error.response.data.deleteremoteaccessvpnresponse
? error.response.data.deleteremoteaccessvpnresponse.errortext : error.response.data.errorresponse.errortext
})
this.fetchData()
this.parentFetchData()
this.parentToggleLoading()
})
}
}
}
</script>
<style scoped>
<style scoped lang="scss">
.actions {
display: flex;
justify-content: flex-end;
button {
&:not(:last-child) {
margin-right: 20px;
}
}
}
</style>