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

View File

@ -13,8 +13,8 @@ var loadLabel = function (allFields, fieldDict, prefix) {
allFields[fieldId].components.push(prefix) allFields[fieldId].components.push(prefix)
} else { } else {
allFields[fieldId] = { allFields[fieldId] = {
'labels': [fieldDict[fieldId].label], labels: [fieldDict[fieldId].label],
'components': [prefix] components: [prefix]
} }
} }
cols = cols + "'" + fieldId + "', " cols = cols + "'" + fieldId + "', "
@ -28,8 +28,8 @@ var loadLabel = function (allFields, fieldDict, prefix) {
allFields[colId].components.push(prefix) allFields[colId].components.push(prefix)
} else { } else {
allFields[colId] = { allFields[colId] = {
'labels': [columns[colId].label], labels: [columns[colId].label],
'components': [prefix] components: [prefix]
} }
} }
}) })
@ -63,9 +63,9 @@ var loadFields = function (data, prefix) {
var curActions = [] var curActions = []
$.each(Object.keys(acVal), function (idx, acKey) { $.each(Object.keys(acVal), function (idx, acKey) {
if (acVal[acKey].createForm) { 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 { } else {
curActions.push({ 'action': acKey, 'label': acVal[acKey].label }) curActions.push({ action: acKey, label: acVal[acKey].label })
} }
}) })
countActions = countActions + curActions.length countActions = countActions + curActions.length
@ -77,5 +77,5 @@ var loadFields = function (data, prefix) {
$.extend(actions, recRes.actions) $.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> </div>
<slot name="name"> <slot name="name">
<h4 class="name"> <h4 class="name">
{{ resource.displayname || resource.name }} {{ resource.displayname || resource.name || resource.displaytext || resource.hostname || resource.username || resource.ipaddress }}
</h4> </h4>
<console style="margin-left: 10px" :resource="resource" size="default" v-if="resource.id" /> <console style="margin-left: 10px" :resource="resource" size="default" v-if="resource.id" />
</slot> </slot>

View File

@ -36,7 +36,7 @@
v-for="tab in tabs" v-for="tab in tabs"
:tab="$t(tab.name)" :tab="$t(tab.name)"
:key="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" /> <component :is="tab.component" :resource="resource" :loading="loading" :tab="activeTab" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
@ -49,6 +49,7 @@
import DetailsTab from '@/components/view/DetailsTab' import DetailsTab from '@/components/view/DetailsTab'
import InfoCard from '@/components/view/InfoCard' import InfoCard from '@/components/view/InfoCard'
import ResourceLayout from '@/layouts/ResourceLayout' import ResourceLayout from '@/layouts/ResourceLayout'
import { api } from '@/api'
export default { export default {
name: 'ResourceView', name: 'ResourceView',
@ -77,12 +78,45 @@ export default {
}, },
data () { data () {
return { 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: { methods: {
onTabChange (key) { onTabChange (key) {
this.activeTab = 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', name: 'details',
component: () => import('@/components/view/DetailsTab.vue') component: () => import('@/components/view/DetailsTab.vue')
}, { }, {
name: 'egress-rules', name: 'Egress Rules',
component: () => import('@/views/network/EgressConfigure.vue') component: () => import('@/views/network/EgressConfigure.vue'),
show: () => true
}], }],
actions: [ actions: [
{ {
@ -227,14 +228,23 @@ export default {
columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'], columns: ['ipaddress', 'state', 'associatednetworkname', 'virtualmachinename', 'allocated', 'account', 'zonename'],
details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename'], details: ['ipaddress', 'id', 'associatednetworkname', 'virtualmachinename', 'networkid', 'issourcenat', 'isstaticnat', 'virtualmachinename', 'vmipaddress', 'vlan', 'allocated', 'account', 'zonename'],
tabs: [{ tabs: [{
name: 'configure',
component: () => import('@/views/network/IpConfigure.vue')
}, {
name: 'vpn',
component: () => import('@/views/network/VpnDetails.vue')
}, {
name: 'details', name: 'details',
component: () => import('@/components/view/DetailsTab.vue') 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: [ actions: [
{ {
@ -244,39 +254,6 @@ export default {
listView: true, listView: true,
args: ['networkid'] 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', api: 'enableStaticNat',
icon: 'plus-circle', icon: 'plus-circle',
@ -306,7 +283,7 @@ export default {
{ {
api: 'disassociateIpAddress', api: 'disassociateIpAddress',
icon: 'delete', icon: 'delete',
label: 'Delete IP', label: 'Release IP',
dataView: true, dataView: true,
show: (record) => { return !record.issourcenat } show: (record) => { return !record.issourcenat }
} }

View File

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

View File

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

View File

@ -17,24 +17,287 @@
<template> <template>
<div> <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> </div>
</template> </template>
<script> <script>
import { api } from '@/api'
export default { export default {
name: '', props: {
components: { resource: {
type: Object,
required: true
}
}, },
data () { data () {
return { 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: { 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> </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> </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" okText="Yes"
cancelText="No" 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> </a-popconfirm>
</div> </div>
</a-list-item> </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. // under the License.
<template> <template>
<div> <div v-if="remoteAccessVpn">
TODO: VPN configure/detail tab view <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> </div>
</template> </template>
<script> <script>
import { api } from '@/api'
export default { export default {
name: '', props: {
components: { resource: {
type: Object,
required: true
}
}, },
data () { data () {
return { 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: { 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> </script>
<style scoped> <style scoped lang="scss">
.actions {
display: flex;
justify-content: flex-end;
button {
&:not(:last-child) {
margin-right: 20px;
}
}
}
</style> </style>